updated libraries
This commit is contained in:
parent
d5d5d000b3
commit
3b7e68065a
102 changed files with 3020 additions and 1624 deletions
|
|
@ -8,6 +8,7 @@ FastLED Library
|
|||
[](http://fastled.io/docs)
|
||||
[](https://www.reddit.com/r/FastLED/)
|
||||
|
||||
|
||||
Want to control a strip of leds? Or control 10's of thousands? FastLED has your back.
|
||||
|
||||
FastLED is a robust and massively parallel-led driver for Arduino, Esp32, RaspberryPi, Atmega, Teensy, Uno, Apollo3 Arm and more. Also runs on dirt cheap sub $1 devices, due to it's incredibly small compile size. High end devices can drive upto ~30k LEDS (Teensy) and ~20k on ESP32. Supports nearly every single LED chipset in existence. Background rendering (ESP32/Teensy/RaspberriPi) means you can respond to user input while the leds render. FastLED is the third [most popular library on Arduino](https://docs.arduino.cc/libraries/).
|
||||
|
|
@ -75,7 +76,11 @@ For more examples, see this [link](examples). Web compiled [examples](https://za
|
|||
|
||||
Video:
|
||||
|
||||
https://github.com/user-attachments/assets/9155124b-a93e-4317-b272-8bacc1b9c3a8
|
||||
|
||||
|
||||
https://github.com/user-attachments/assets/ff8e0432-3e0d-47cc-a444-82ce27f562af
|
||||
|
||||
|
||||
|
||||
#### Major release for tech-artists!
|
||||
|
||||
|
|
@ -248,6 +253,10 @@ Update: max overclock has been reported at +70%: https://www.reddit.com/r/FastLE
|
|||
|
||||
[](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml)
|
||||
|
||||
|
||||
[](https://github.com/FastLED/FastLED/actions/workflows/build_esp32p4.yml)
|
||||
|
||||
|
||||
*Specific features*
|
||||
|
||||
[](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml)
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ Release notes should list highlight changes (not necessarily all minor bug fixes
|
|||
|
||||
Git commands to commit and tag release'
|
||||
```bash
|
||||
$ git commit -am "Rev 3.9.20 - Misc fixes"
|
||||
$ git tag 3.9.20 master
|
||||
$ git commit -am "Rev 3.10.1 - Bug fix for 3.10.0"
|
||||
$ git tag 3.10.1 master
|
||||
$ git push
|
||||
$ git push origin 3.9.20
|
||||
$ git push origin 3.10.1
|
||||
```
|
||||
|
||||
Then use the GitHub UI to make a new “Release”:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from ci.locked_print import locked_print
|
|||
|
||||
HERE = Path(__file__).parent.resolve()
|
||||
|
||||
LIBS = ["src", "ci"]
|
||||
LIBS = ["src"]
|
||||
EXTRA_LIBS = [
|
||||
"https://github.com/me-no-dev/ESPAsyncWebServer.git",
|
||||
"ArduinoOTA",
|
||||
|
|
@ -45,6 +45,7 @@ DEFAULT_BOARDS_NAMES = [
|
|||
"ATtiny1616",
|
||||
"esp32c6",
|
||||
"esp32s3",
|
||||
"esp32p4",
|
||||
"yun",
|
||||
"digix",
|
||||
"teensy30",
|
||||
|
|
@ -69,16 +70,22 @@ OTHER_BOARDS_NAMES = [
|
|||
|
||||
# Examples to compile.
|
||||
DEFAULT_EXAMPLES = [
|
||||
"Animartrix",
|
||||
"Apa102",
|
||||
"Apa102HD",
|
||||
"Apa102HDOverride",
|
||||
"Audio",
|
||||
"Blink",
|
||||
"Blur",
|
||||
"Chromancer",
|
||||
"ColorPalette",
|
||||
"ColorTemperature",
|
||||
"Corkscrew",
|
||||
"CompileTest",
|
||||
"Cylon",
|
||||
"DemoReel100",
|
||||
"Downscale",
|
||||
"FestivalStick",
|
||||
"FirstLight",
|
||||
"Fire2012",
|
||||
"Multiple/MultipleStripsInOneArray",
|
||||
|
|
@ -96,6 +103,8 @@ DEFAULT_EXAMPLES = [
|
|||
"RGBWEmulated",
|
||||
"TwinkleFox",
|
||||
"XYMatrix",
|
||||
"FireMatrix",
|
||||
"FireCylinder",
|
||||
"FxGfx2Video",
|
||||
"FxSdCard",
|
||||
"FxCylon",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ ESP32_IDF_5_1_PIOARDUINO = "https://github.com/pioarduino/platform-espressif32/r
|
|||
|
||||
# TODO: Upgrade toolkit to 5.3
|
||||
ESP32_IDF_5_3_PIOARDUINO = "https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10/platform-espressif32.zip"
|
||||
ESP32_IDF_5_4_PIOARDUINO = "https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip"
|
||||
ESP32_IDF_5_1_PIOARDUINO_LATEST = (
|
||||
"https://github.com/pioarduino/platform-espressif32.git#develop"
|
||||
)
|
||||
|
|
@ -22,6 +23,8 @@ APOLLO3_2_2_0 = "https://github.com/nigelb/platform-apollo3blue"
|
|||
# Old fork that we were using
|
||||
# ESP32_IDF_5_1_PIOARDUINO = "https://github.com/zackees/platform-espressif32#Arduino/IDF5"
|
||||
|
||||
# ALL will be auto populated in the Board constructor whenever a
|
||||
# board is defined.
|
||||
ALL: list["Board"] = []
|
||||
|
||||
|
||||
|
|
@ -156,7 +159,7 @@ ESP32_C2_DEVKITM_1 = Board(
|
|||
board_name="esp32c2",
|
||||
real_board_name="esp32-c2-devkitm-1",
|
||||
use_pio_run=True,
|
||||
platform="https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5",
|
||||
platform="https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip",
|
||||
defines=["CONFIG_IDF_TARGET_ESP32C2=1"],
|
||||
)
|
||||
|
||||
|
|
@ -175,14 +178,7 @@ ESP32_C6_DEVKITC_1 = Board(
|
|||
ESP32_S3_DEVKITC_1 = Board(
|
||||
board_name="esp32s3",
|
||||
real_board_name="seeed_xiao_esp32s3", # Seeed Xiao ESP32-S3 has psram.
|
||||
platform=ESP32_IDF_5_3_PIOARDUINO,
|
||||
defines=[
|
||||
"BOARD_HAS_PSRAM",
|
||||
],
|
||||
build_flags=[ # Reserved for future use.
|
||||
"-mfix-esp32-psram-cache-issue",
|
||||
"-mfix-esp32-psram-cache-strategy=memw",
|
||||
],
|
||||
platform=ESP32_IDF_5_4_PIOARDUINO,
|
||||
board_partitions="huge_app.csv", # Reserved for future use.
|
||||
)
|
||||
|
||||
|
|
@ -199,6 +195,13 @@ ESP32_H2_DEVKITM_1 = Board(
|
|||
platform=ESP32_IDF_5_3_PIOARDUINO,
|
||||
)
|
||||
|
||||
ESP32_P4 = Board(
|
||||
board_name="esp32p4",
|
||||
real_board_name="esp32-p4-evboard",
|
||||
platform_needs_install=True, # Install platform package to get the boards
|
||||
platform="https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip",
|
||||
)
|
||||
|
||||
ADA_FEATHER_NRF52840_SENSE = Board(
|
||||
board_name="adafruit_feather_nrf52840_sense",
|
||||
platform="nordicnrf52",
|
||||
|
|
|
|||
215
libraries/FastLED/ci/ci/check_files.py
Normal file
215
libraries/FastLED/ci/ci/check_files.py
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from ci.paths import PROJECT_ROOT
|
||||
|
||||
SRC_ROOT = PROJECT_ROOT / "src"
|
||||
|
||||
NUM_WORKERS = (os.cpu_count() or 1) * 4
|
||||
|
||||
EXCLUDED_FILES = [
|
||||
"stub_main.cpp",
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class FileContent:
|
||||
"""Container for file content and metadata."""
|
||||
|
||||
path: str
|
||||
content: str
|
||||
lines: List[str]
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.lines:
|
||||
self.lines = self.content.splitlines()
|
||||
|
||||
|
||||
class FileContentChecker(ABC):
|
||||
"""Abstract base class for checking file content."""
|
||||
|
||||
@abstractmethod
|
||||
def should_process_file(self, file_path: str) -> bool:
|
||||
"""Predicate to determine if a file should be processed.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to check
|
||||
|
||||
Returns:
|
||||
True if the file should be processed, False otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def check_file_content(self, file_content: FileContent) -> List[str]:
|
||||
"""Check the file content and return any issues found.
|
||||
|
||||
Args:
|
||||
file_content: FileContent object containing path, content, and lines
|
||||
|
||||
Returns:
|
||||
List of error messages, empty if no issues found
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MultiCheckerFileProcessor:
|
||||
"""Processor that can run multiple checkers on files."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def process_files_with_checkers(
|
||||
self, file_paths: List[str], checkers: List[FileContentChecker]
|
||||
) -> Dict[str, List[str]]:
|
||||
"""Process files with multiple checkers.
|
||||
|
||||
Args:
|
||||
file_paths: List of file paths to process
|
||||
checkers: List of checker instances to run on the files
|
||||
|
||||
Returns:
|
||||
Dictionary mapping checker class name to list of issues found
|
||||
"""
|
||||
# Initialize results dictionary for each checker
|
||||
results = {}
|
||||
for checker in checkers:
|
||||
checker_name = checker.__class__.__name__
|
||||
results[checker_name] = []
|
||||
|
||||
# Process each file
|
||||
for file_path in file_paths:
|
||||
# Check if any checker wants to process this file
|
||||
interested_checkers = [
|
||||
checker
|
||||
for checker in checkers
|
||||
if checker.should_process_file(file_path)
|
||||
]
|
||||
|
||||
# If any checker is interested, read the file once
|
||||
if interested_checkers:
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Create FileContent object with lines split
|
||||
file_content = FileContent(
|
||||
path=file_path, content=content, lines=content.splitlines()
|
||||
)
|
||||
|
||||
# Pass the file content to all interested checkers
|
||||
for checker in interested_checkers:
|
||||
checker_name = checker.__class__.__name__
|
||||
issues = checker.check_file_content(file_content)
|
||||
results[checker_name].extend(issues)
|
||||
|
||||
except Exception as e:
|
||||
# Add error to all interested checkers
|
||||
error_msg = f"Error reading file {file_path}: {str(e)}"
|
||||
for checker in interested_checkers:
|
||||
checker_name = checker.__class__.__name__
|
||||
results[checker_name].append(error_msg)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# Legacy compatibility classes
|
||||
class FileProcessorCallback(FileContentChecker):
|
||||
"""Legacy compatibility wrapper - delegates to FileContentChecker methods."""
|
||||
|
||||
def check_file_content_legacy(self, file_path: str, content: str) -> List[str]:
|
||||
"""Legacy method signature for backward compatibility."""
|
||||
file_content = FileContent(path=file_path, content=content, lines=[])
|
||||
return self.check_file_content(file_content)
|
||||
|
||||
|
||||
class GenericFileSearcher:
|
||||
"""Generic file searcher that processes files using a callback pattern."""
|
||||
|
||||
def __init__(self, max_workers: Optional[int] = None):
|
||||
self.max_workers = max_workers or NUM_WORKERS
|
||||
|
||||
def search_directory(
|
||||
self, start_dir: str, callback: FileProcessorCallback
|
||||
) -> List[str]:
|
||||
"""Search a directory and process files using the provided callback.
|
||||
|
||||
Args:
|
||||
start_dir: Directory to start searching from
|
||||
callback: Callback class to handle file processing
|
||||
|
||||
Returns:
|
||||
List of all issues found across all files
|
||||
"""
|
||||
files_to_check = []
|
||||
|
||||
# Collect all files that should be processed
|
||||
for root, _, files in os.walk(start_dir):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
if callback.should_process_file(file_path):
|
||||
files_to_check.append(file_path)
|
||||
|
||||
# Process files in parallel
|
||||
all_issues = []
|
||||
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
|
||||
futures = [
|
||||
executor.submit(self._process_single_file, file_path, callback)
|
||||
for file_path in files_to_check
|
||||
]
|
||||
for future in futures:
|
||||
all_issues.extend(future.result())
|
||||
|
||||
return all_issues
|
||||
|
||||
def _process_single_file(
|
||||
self, file_path: str, callback: FileProcessorCallback
|
||||
) -> List[str]:
|
||||
"""Process a single file using the callback.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to process
|
||||
callback: Callback to use for processing
|
||||
|
||||
Returns:
|
||||
List of issues found in this file
|
||||
"""
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
file_content = FileContent(path=file_path, content=content, lines=[])
|
||||
return callback.check_file_content(file_content)
|
||||
except Exception as e:
|
||||
return [f"Error processing file {file_path}: {str(e)}"]
|
||||
|
||||
|
||||
def collect_files_to_check(
|
||||
test_directories: List[str], extensions: List[str] | None = None
|
||||
) -> List[str]:
|
||||
"""Collect all files to check from the given directories."""
|
||||
if extensions is None:
|
||||
extensions = [".cpp", ".h", ".hpp"]
|
||||
|
||||
files_to_check = []
|
||||
|
||||
# Search each directory
|
||||
for directory in test_directories:
|
||||
if os.path.exists(directory):
|
||||
for root, _, files in os.walk(directory):
|
||||
for file in files:
|
||||
if any(file.endswith(ext) for ext in extensions):
|
||||
file_path = os.path.join(root, file)
|
||||
files_to_check.append(file_path)
|
||||
|
||||
# Also check the main src directory files (not subdirectories)
|
||||
for file in os.listdir(SRC_ROOT):
|
||||
file_path = os.path.join(SRC_ROOT, file)
|
||||
if os.path.isfile(file_path) and any(
|
||||
file_path.endswith(ext) for ext in extensions
|
||||
):
|
||||
files_to_check.append(file_path)
|
||||
|
||||
return files_to_check
|
||||
|
|
@ -33,7 +33,7 @@ def main() -> int:
|
|||
board_dir = board_dirs[which]
|
||||
# build_info_json = board_dir / "build_info.json"
|
||||
optimization_report = board_dir / "optimization_report.txt"
|
||||
text = optimization_report.read_text(encoding="utf-8")
|
||||
text = optimization_report.read_text(encoding="utf-8", errors="replace")
|
||||
print(text)
|
||||
return 0
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
import os
|
||||
import unittest
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Callable, List
|
||||
|
||||
from ci.check_files import (
|
||||
EXCLUDED_FILES,
|
||||
FileContent,
|
||||
FileContentChecker,
|
||||
MultiCheckerFileProcessor,
|
||||
collect_files_to_check,
|
||||
)
|
||||
from ci.paths import PROJECT_ROOT
|
||||
|
||||
SRC_ROOT = PROJECT_ROOT / "src"
|
||||
PLATFORMS_DIR = os.path.join(SRC_ROOT, "platforms")
|
||||
PLATFORMS_ESP_DIR = os.path.join(PLATFORMS_DIR, "esp")
|
||||
|
||||
NUM_WORKERS = (os.cpu_count() or 1) * 4
|
||||
|
||||
ENABLE_PARANOID_GNU_HEADER_INSPECTION = False
|
||||
|
||||
|
|
@ -17,8 +20,7 @@ if ENABLE_PARANOID_GNU_HEADER_INSPECTION:
|
|||
else:
|
||||
BANNED_HEADERS_ESP = []
|
||||
|
||||
|
||||
BANNED_HEADERS_CORE = [
|
||||
BANNED_HEADERS_COMMON = [
|
||||
"assert.h",
|
||||
"iostream",
|
||||
"stdio.h",
|
||||
|
|
@ -56,76 +58,123 @@ BANNED_HEADERS_CORE = [
|
|||
"cstdint",
|
||||
"cstddef", # this certainally fails
|
||||
"type_traits", # this certainally fails
|
||||
"Arduino.h",
|
||||
] + BANNED_HEADERS_ESP
|
||||
|
||||
EXCLUDED_FILES = [
|
||||
"stub_main.cpp",
|
||||
]
|
||||
|
||||
BANNED_HEADERS_CORE = BANNED_HEADERS_COMMON + BANNED_HEADERS_ESP + ["Arduino.h"]
|
||||
|
||||
|
||||
class BannedHeadersChecker(FileContentChecker):
|
||||
"""Checker class for banned headers."""
|
||||
|
||||
def __init__(self, banned_headers_list: List[str]):
|
||||
"""Initialize with the list of banned headers to check for."""
|
||||
self.banned_headers_list = banned_headers_list
|
||||
|
||||
def should_process_file(self, file_path: str) -> bool:
|
||||
"""Check if file should be processed for banned headers."""
|
||||
# Check file extension
|
||||
if not file_path.endswith((".cpp", ".h", ".hpp", ".ino")):
|
||||
return False
|
||||
|
||||
# Check if file is in excluded list
|
||||
if any(file_path.endswith(excluded) for excluded in EXCLUDED_FILES):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def check_file_content(self, file_content: FileContent) -> List[str]:
|
||||
"""Check file content for banned headers."""
|
||||
failings = []
|
||||
|
||||
if len(self.banned_headers_list) == 0:
|
||||
return failings
|
||||
|
||||
# Check each line for banned headers
|
||||
for line_number, line in enumerate(file_content.lines, 1):
|
||||
if line.strip().startswith("//"):
|
||||
continue
|
||||
|
||||
for header in self.banned_headers_list:
|
||||
if (
|
||||
f"#include <{header}>" in line or f'#include "{header}"' in line
|
||||
) and "// ok include" not in line:
|
||||
failings.append(
|
||||
f"Found banned header '{header}' in {file_content.path}:{line_number}"
|
||||
)
|
||||
|
||||
return failings
|
||||
|
||||
|
||||
def _test_no_banned_headers(
|
||||
test_directories: list[str],
|
||||
banned_headers_list: List[str],
|
||||
on_fail: Callable[[str], None],
|
||||
) -> None:
|
||||
"""Searches through the program files to check for banned headers."""
|
||||
# Collect files to check
|
||||
files_to_check = collect_files_to_check(test_directories)
|
||||
|
||||
# Create processor and checker
|
||||
processor = MultiCheckerFileProcessor()
|
||||
checker = BannedHeadersChecker(banned_headers_list)
|
||||
|
||||
# Process files
|
||||
results = processor.process_files_with_checkers(files_to_check, [checker])
|
||||
|
||||
# Get results for banned headers checker
|
||||
all_failings = results.get("BannedHeadersChecker", []) or []
|
||||
|
||||
if all_failings:
|
||||
msg = f"Found {len(all_failings)} banned header(s): \n" + "\n".join(
|
||||
all_failings
|
||||
)
|
||||
for failing in all_failings:
|
||||
print(failing)
|
||||
|
||||
on_fail(msg)
|
||||
else:
|
||||
print("No banned headers found.")
|
||||
|
||||
|
||||
class TestNoBannedHeaders(unittest.TestCase):
|
||||
|
||||
def check_file(self, file_path: str) -> list[str]:
|
||||
failings: list[str] = []
|
||||
banned_headers_list = []
|
||||
if file_path.startswith(PLATFORMS_DIR):
|
||||
# continue # Skip the platforms directory
|
||||
if file_path.startswith(PLATFORMS_ESP_DIR):
|
||||
banned_headers_list = BANNED_HEADERS_ESP
|
||||
else:
|
||||
return failings
|
||||
if len(banned_headers_list) == 0:
|
||||
return failings
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
def test_no_banned_headers_src(self) -> None:
|
||||
"""Searches through the program files to check for banned headers."""
|
||||
|
||||
for line_number, line in enumerate(f, 1):
|
||||
if line.startswith("//"):
|
||||
continue
|
||||
for header in banned_headers_list:
|
||||
if (
|
||||
f"#include <{header}>" in line or f'#include "{header}"' in line
|
||||
) and "// ok include" not in line:
|
||||
failings.append(
|
||||
f"Found banned header '{header}' in {file_path}:{line_number}"
|
||||
)
|
||||
return failings
|
||||
|
||||
def test_no_banned_headers(self) -> None:
|
||||
"""Searches through the program files to check for banned headers, excluding src/platforms."""
|
||||
files_to_check = []
|
||||
for root, _, files in os.walk(SRC_ROOT):
|
||||
for file in files:
|
||||
if file.endswith(
|
||||
(".cpp", ".h", ".hpp")
|
||||
): # Add or remove file extensions as needed
|
||||
file_path = os.path.join(root, file)
|
||||
if not any(
|
||||
file_path.endswith(excluded) for excluded in EXCLUDED_FILES
|
||||
):
|
||||
files_to_check.append(file_path)
|
||||
|
||||
all_failings = []
|
||||
with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:
|
||||
futures = [
|
||||
executor.submit(self.check_file, file_path)
|
||||
for file_path in files_to_check
|
||||
]
|
||||
for future in futures:
|
||||
all_failings.extend(future.result())
|
||||
|
||||
if all_failings:
|
||||
msg = f"Found {len(all_failings)} banned header(s): \n" + "\n".join(
|
||||
all_failings
|
||||
)
|
||||
for failing in all_failings:
|
||||
print(failing)
|
||||
def on_fail(msg: str) -> None:
|
||||
self.fail(
|
||||
msg + "\n"
|
||||
"You can add '// ok include' at the end of the line to silence this error for specific inclusions."
|
||||
)
|
||||
else:
|
||||
print("No banned headers found.")
|
||||
|
||||
# Test directories as requested
|
||||
test_directories = [
|
||||
os.path.join(SRC_ROOT, "fl"),
|
||||
os.path.join(SRC_ROOT, "fx"),
|
||||
os.path.join(SRC_ROOT, "sensors"),
|
||||
]
|
||||
_test_no_banned_headers(
|
||||
test_directories=test_directories,
|
||||
banned_headers_list=BANNED_HEADERS_CORE,
|
||||
on_fail=on_fail,
|
||||
)
|
||||
|
||||
def test_no_banned_headers_examples(self) -> None:
|
||||
"""Searches through the program files to check for banned headers."""
|
||||
|
||||
def on_fail(msg: str) -> None:
|
||||
self.fail(
|
||||
msg + "\n"
|
||||
"You can add '// ok include' at the end of the line to silence this error for specific inclusions."
|
||||
)
|
||||
|
||||
test_directories = ["examples"]
|
||||
|
||||
_test_no_banned_headers(
|
||||
test_directories=test_directories,
|
||||
banned_headers_list=BANNED_HEADERS_COMMON,
|
||||
on_fail=on_fail,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ PROJECT_NAME = FastLED
|
|||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 3.9.20
|
||||
PROJECT_NUMBER = 3.10.1
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewers a
|
||||
|
|
|
|||
|
|
@ -3,10 +3,31 @@
|
|||
/// @author Stefan Petrick
|
||||
/// @author Zach Vorhies (FastLED adaptation)
|
||||
///
|
||||
/// This sketch is fully compatible with the FastLED web compiler. To use it do the following:
|
||||
/// 1. Install Fastled: `pip install fastled`
|
||||
/// 2. cd into this examples page.
|
||||
/// 3. Run the FastLED web compiler at root: `fastled`
|
||||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
all the UI elements you see below.
|
||||
|
||||
|
||||
|
||||
OVERVIEW:
|
||||
This is the famouse Animartrix demo by Stefan Petrick. The effects are generated
|
||||
using polor polar coordinates. The effects are very complex and powerful.
|
||||
*/
|
||||
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Platform does not have enough memory
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
|
|
@ -34,6 +55,10 @@ using namespace fl;
|
|||
|
||||
#define FIRST_ANIMATION POLAR_WAVES
|
||||
|
||||
// This is purely use for the web compiler to display the animartrix effects.
|
||||
// This small led was chosen because otherwise the bloom effect is too strong.
|
||||
#define LED_DIAMETER 0.15 // .15 cm or 1.5mm
|
||||
|
||||
|
||||
CRGB leds[NUM_LEDS];
|
||||
XYMap xyMap = XYMap::constructRectangularGrid(MATRIX_WIDTH, MATRIX_HEIGHT);
|
||||
|
|
@ -44,19 +69,33 @@ UIDescription description("Demo of the Animatrix effects. @author of fx is Stefa
|
|||
|
||||
UISlider brightness("Brightness", 255, 0, 255);
|
||||
UINumberField fxIndex("Animartrix - index", 0, 0, NUM_ANIMATIONS - 1);
|
||||
UINumberField colorOrder("Color Order", 0, 0, 5);
|
||||
UISlider timeSpeed("Time Speed", 1, -10, 10, .1);
|
||||
|
||||
Animartrix animartrix(xyMap, FIRST_ANIMATION);
|
||||
FxEngine fxEngine(NUM_LEDS);
|
||||
|
||||
|
||||
void setup() {
|
||||
auto screen_map = xyMap.toScreenMap();
|
||||
screen_map.setDiameter(.1);
|
||||
screen_map.setDiameter(LED_DIAMETER);
|
||||
FastLED.addLeds<WS2811, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
|
||||
.setCorrection(TypicalLEDStrip)
|
||||
.setScreenMap(screen_map);
|
||||
FastLED.setBrightness(brightness);
|
||||
fxEngine.addFx(animartrix);
|
||||
|
||||
colorOrder.onChanged([](int value) {
|
||||
switch(value) {
|
||||
case 0: value = RGB; break;
|
||||
case 1: value = RBG; break;
|
||||
case 2: value = GRB; break;
|
||||
case 3: value = GBR; break;
|
||||
case 4: value = BRG; break;
|
||||
case 5: value = BGR; break;
|
||||
}
|
||||
animartrix.setColorOrder(static_cast<EOrder>(value));
|
||||
});
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
|
@ -72,3 +111,4 @@ void loop() {
|
|||
}
|
||||
|
||||
|
||||
#endif // __AVR__
|
||||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
@ -11,6 +16,14 @@ all the UI elements you see below.
|
|||
#include <Arduino.h>
|
||||
#include <FastLED.h>
|
||||
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Platform does not have enough memory
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else
|
||||
|
||||
|
||||
|
||||
#include "fl/audio.h"
|
||||
#include "fl/downscale.h"
|
||||
#include "fl/draw_visitor.h"
|
||||
|
|
@ -21,9 +34,11 @@ all the UI elements you see below.
|
|||
#include "fl/time_alpha.h"
|
||||
#include "fl/ui.h"
|
||||
#include "fl/xypath.h"
|
||||
#include "fx.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fx_audio.h"
|
||||
#include "fx/time.h"
|
||||
|
||||
|
||||
// Sketch.
|
||||
#include "fl/function.h"
|
||||
|
||||
|
|
@ -34,6 +49,7 @@ using namespace fl;
|
|||
#define NUM_LEDS ((WIDTH) * (HEIGHT))
|
||||
#define IS_SERPINTINE false
|
||||
#define TIME_ANIMATION 1000 // ms
|
||||
#define PIN_DATA 3
|
||||
|
||||
UITitle title("Simple control of an xy path");
|
||||
UIDescription description("This is more of a test for new features.");
|
||||
|
|
@ -106,7 +122,7 @@ void setup() {
|
|||
audioFadeTracker.setOutputTime(value);
|
||||
FASTLED_WARN("Output time seconds: " << value);
|
||||
});
|
||||
FastLED.addLeds<NEOPIXEL, 2>(leds, ledsXY.getTotal())
|
||||
FastLED.addLeds<NEOPIXEL, PIN_DATA>(leds, ledsXY.getTotal())
|
||||
.setScreenMap(screenmap);
|
||||
}
|
||||
|
||||
|
|
@ -144,10 +160,6 @@ void loop() {
|
|||
if (triggered) {
|
||||
FASTLED_WARN("Triggered");
|
||||
}
|
||||
// fl::clear(framebuffer);
|
||||
// fl::clear(framebuffer);
|
||||
|
||||
static uint32_t frame = 0;
|
||||
|
||||
// x = pointX.as_int();
|
||||
y = HEIGHT / 2;
|
||||
|
|
@ -164,6 +176,7 @@ void loop() {
|
|||
soundLevelMeter.processBlock(sample.pcm());
|
||||
// FASTLED_WARN("")
|
||||
auto dbfs = soundLevelMeter.getDBFS();
|
||||
FASTLED_UNUSED(dbfs);
|
||||
// FASTLED_WARN("getDBFS: " << dbfs);
|
||||
int32_t max = 0;
|
||||
for (int i = 0; i < sample.pcm().size(); ++i) {
|
||||
|
|
@ -187,6 +200,7 @@ void loop() {
|
|||
|
||||
if (enableFFT) {
|
||||
auto max_x = fftOut.bins_raw.size() - 1;
|
||||
FASTLED_UNUSED(max_x);
|
||||
for (int i = 0; i < fftOut.bins_raw.size(); ++i) {
|
||||
auto x = i;
|
||||
auto v = fftOut.bins_db[i];
|
||||
|
|
@ -232,3 +246,5 @@ void loop() {
|
|||
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
#endif // __AVR__
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
#include "fl/time_alpha.h"
|
||||
#include "fl/math_macros.h"
|
||||
|
||||
/// Tracks a smoothed peak with attack, decay, and output-inertia time-constants.
|
||||
class MaxFadeTracker {
|
||||
|
|
@ -34,8 +32,8 @@ public:
|
|||
// 1) block peak
|
||||
float peak = 0.0f;
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
float v = std::abs(samples[i]) * (1.0f/32768.0f);
|
||||
peak = std::max(peak, v);
|
||||
float v = ABS(samples[i]) * (1.0f/32768.0f);
|
||||
peak = MAX(peak, v);
|
||||
}
|
||||
|
||||
// 2) time delta
|
||||
|
|
@ -43,15 +41,15 @@ public:
|
|||
|
||||
// 3) update currentLevel_ with attack/decay
|
||||
if (peak > currentLevel_) {
|
||||
float riseFactor = 1.0f - std::exp(-attackRate_ * dt);
|
||||
float riseFactor = 1.0f - exp(-attackRate_ * dt);
|
||||
currentLevel_ += (peak - currentLevel_) * riseFactor;
|
||||
} else {
|
||||
float decayFactor = std::exp(-decayRate_ * dt);
|
||||
float decayFactor = exp(-decayRate_ * dt);
|
||||
currentLevel_ *= decayFactor;
|
||||
}
|
||||
|
||||
// 4) output inertia: smooth smoothedOutput_ → currentLevel_
|
||||
float outFactor = 1.0f - std::exp(-outputRate_ * dt);
|
||||
float outFactor = 1.0f - exp(-outputRate_ * dt);
|
||||
smoothedOutput_ += (currentLevel_ - smoothedOutput_) * outFactor;
|
||||
|
||||
return smoothedOutput_;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
*/
|
||||
|
||||
#if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_TEENSYLC)
|
||||
// Avr is not powerful enough.
|
||||
// Platform does not have enough memory
|
||||
// Other platforms have weird issues. Will revisit this later.
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
|
|
|
|||
18
libraries/FastLED/examples/CompileTest/CompileTest.ino
Normal file
18
libraries/FastLED/examples/CompileTest/CompileTest.ino
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include "FastLED.h"
|
||||
|
||||
#if defined(__AVR__)
|
||||
#include "avr_test.h"
|
||||
#elif defined(ESP32)
|
||||
#include "esp_test.h"
|
||||
#endif
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Setup");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Serial.println("Loop");
|
||||
delay(100);
|
||||
}
|
||||
13
libraries/FastLED/examples/CompileTest/avr_test.h
Normal file
13
libraries/FastLED/examples/CompileTest/avr_test.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
void avr_tests() {
|
||||
#if FASTLED_USE_PROGMEM != 1
|
||||
#error "FASTLED_USE_PROGMEM is not 1 for AVR"
|
||||
#endif
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY != 0
|
||||
#error "SKETCH_HAS_LOTS_OF_MEMORY is not 0 for AVR"
|
||||
#endif
|
||||
}
|
||||
13
libraries/FastLED/examples/CompileTest/esp_8266_test.h
Normal file
13
libraries/FastLED/examples/CompileTest/esp_8266_test.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
void esp_8266_tests() {
|
||||
#if FASTLED_USE_PROGMEM != 1
|
||||
#error "FASTLED_USE_PROGMEM is not 1 for AVR"
|
||||
#endif
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY != 0
|
||||
#error "SKETCH_HAS_LOTS_OF_MEMORY is not 0 for AVR"
|
||||
#endif
|
||||
}
|
||||
13
libraries/FastLED/examples/CompileTest/esp_s3_test.h
Normal file
13
libraries/FastLED/examples/CompileTest/esp_s3_test.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
void esp32_s3_tests() {
|
||||
#if FASTLED_USE_PROGMEM != 0
|
||||
#error "FASTLED_USE_PROGMEM prog memory is turned on for esap32-s3"
|
||||
#endif
|
||||
|
||||
#if SKETCH_HAS_LOTS_OF_MEMORY == 0
|
||||
#error "SKETCH_HAS_LOTS_OF_MEMORY should have lots of memory for esp32-s3"
|
||||
#endif
|
||||
}
|
||||
10
libraries/FastLED/examples/CompileTest/esp_test.h
Normal file
10
libraries/FastLED/examples/CompileTest/esp_test.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include "esp_8266_test.h"
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#include "esp_s3_test.h"
|
||||
#endif
|
||||
134
libraries/FastLED/examples/Corkscrew/Corkscrew.h
Normal file
134
libraries/FastLED/examples/Corkscrew/Corkscrew.h
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
Basic cork screw test.
|
||||
|
||||
This test is forward mapping, in which we test that
|
||||
the corkscrew is mapped to cylinder cartesian coordinates.
|
||||
|
||||
Most of the time, you'll want the reverse mapping, that is
|
||||
drawing to a rectangular grid, and then mapping that to a corkscrew.
|
||||
|
||||
However, to make sure the above mapping works correctly, we have
|
||||
to test that the forward mapping works correctly first.
|
||||
|
||||
*/
|
||||
|
||||
#include "fl/assert.h"
|
||||
#include "fl/corkscrew.h"
|
||||
#include "fl/grid.h"
|
||||
#include "fl/leds.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/sstream.h"
|
||||
#include "fl/warn.h"
|
||||
#include "noise.h"
|
||||
#include <FastLED.h>
|
||||
// #include "vec3.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
#define PIN_DATA 9
|
||||
|
||||
#define NUM_LEDS 288
|
||||
#define CORKSCREW_TOTAL_LENGTH 100
|
||||
#define CORKSCREW_TOTAL_HEIGHT 23.25 // when height = 0, it's a circle.
|
||||
// wrapped up over 19 turns
|
||||
#define CORKSCREW_TURNS 19 // Default to 19 turns
|
||||
|
||||
// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs
|
||||
// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter
|
||||
|
||||
UITitle festivalStickTitle("Corkscrew");
|
||||
UIDescription festivalStickDescription(
|
||||
"Tests the ability to map a cork screw onto a 2D cylindrical surface");
|
||||
|
||||
UISlider speed("Speed", 0.1f, 0.01f, 1.0f, 0.01f);
|
||||
|
||||
UICheckbox allWhite("All White", false);
|
||||
UICheckbox splatRendering("Splat Rendering", true);
|
||||
|
||||
// CRGB leds[NUM_LEDS];
|
||||
|
||||
// Tested on a 288 led (2x 144 max density led strip) with 19 turns
|
||||
// with 23.25cm height, 19 turns, and ~15.5 LEDs per turn.
|
||||
Corkscrew::Input corkscrewInput(CORKSCREW_TOTAL_LENGTH, CORKSCREW_TOTAL_HEIGHT,
|
||||
CORKSCREW_TURNS, // Default to 19 turns
|
||||
NUM_LEDS, // Default to dense 144 leds.
|
||||
0 // offset to account for gaps between segments
|
||||
);
|
||||
|
||||
// Corkscrew::State corkscrewMap = fl::Corkscrew::generateMap(corkscrewInput);
|
||||
Corkscrew corkscrew(corkscrewInput);
|
||||
|
||||
// Create a corkscrew with:
|
||||
// - 30cm total length (300mm)
|
||||
// - 5cm width (50mm)
|
||||
// - 2mm LED inner diameter
|
||||
// - 24 LEDs per turn
|
||||
// fl::ScreenMap screenMap = makeCorkScrew(NUM_LEDS,
|
||||
// 300.0f, 50.0f, 2.0f, 24.0f);
|
||||
|
||||
// fl::vector<vec3f> mapCorkScrew = makeCorkScrew(args);
|
||||
fl::ScreenMap screenMap;
|
||||
fl::Grid<CRGB> frameBuffer;
|
||||
|
||||
void setup() {
|
||||
int width = corkscrew.cylinder_width();
|
||||
int height = corkscrew.cylinder_height();
|
||||
|
||||
frameBuffer.reset(width, height);
|
||||
XYMap xyMap = XYMap::constructRectangularGrid(width, height, 0);
|
||||
|
||||
CRGB *leds = frameBuffer.data();
|
||||
size_t num_leds = frameBuffer.size();
|
||||
|
||||
CLEDController *controller =
|
||||
&FastLED.addLeds<WS2812, 3, BGR>(leds, num_leds);
|
||||
|
||||
fl::ScreenMap screenMap = xyMap.toScreenMap();
|
||||
screenMap.setDiameter(.2f);
|
||||
|
||||
// Set the screen map for the controller
|
||||
controller->setScreenMap(screenMap);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
fl::clear(frameBuffer);
|
||||
static float pos = 0;
|
||||
pos += speed.value();
|
||||
if (pos > corkscrew.size() - 1) {
|
||||
pos = 0; // Reset to the beginning
|
||||
}
|
||||
|
||||
if (allWhite) {
|
||||
for (size_t i = 0; i < frameBuffer.size(); ++i) {
|
||||
frameBuffer.data()[i] = CRGB(8, 8, 8);
|
||||
}
|
||||
}
|
||||
|
||||
if (splatRendering) {
|
||||
Tile2x2_u8_wrap pos_tile = corkscrew.at_wrap(pos);
|
||||
const CRGB color = CRGB::Blue;
|
||||
// Draw each pixel in the 2x2 tile using the new wrapping API
|
||||
for (int dx = 0; dx < 2; ++dx) {
|
||||
for (int dy = 0; dy < 2; ++dy) {
|
||||
auto data = pos_tile.at(dx, dy);
|
||||
vec2i16 wrapped_pos = data.first; // Already wrapped position
|
||||
uint8_t alpha = data.second; // Alpha value
|
||||
|
||||
if (alpha > 0) { // Only draw if there's some alpha
|
||||
CRGB c = color;
|
||||
c.nscale8(alpha); // Scale the color by the alpha value
|
||||
frameBuffer.at(wrapped_pos.x, wrapped_pos.y) = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// None splat rendering, looks aweful.
|
||||
vec2f pos_vec2f = corkscrew.at_exact(pos);
|
||||
vec2i16 pos_i16 = vec2i16(round(pos_vec2f.x), round(pos_vec2f.y));
|
||||
// Now map the cork screw position to the cylindrical buffer that we
|
||||
// will draw.
|
||||
frameBuffer.at(pos_i16.x, pos_i16.y) =
|
||||
CRGB::Blue; // Draw a blue pixel at (w, h)
|
||||
}
|
||||
FastLED.show();
|
||||
}
|
||||
11
libraries/FastLED/examples/Corkscrew/Corkscrew.ino
Normal file
11
libraries/FastLED/examples/Corkscrew/Corkscrew.ino
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#include <FastLED.h> // Main FastLED library for controlling LEDs
|
||||
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically
|
||||
// don't have enough memory to handle this complex animation.
|
||||
// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing.
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else // For all other platforms with more memory (ESP32, Teensy, etc.)
|
||||
#include "Corkscrew.h"
|
||||
#endif // End of the non-AVR code section
|
||||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
@ -11,6 +16,7 @@ all the UI elements you see below.
|
|||
#include <Arduino.h>
|
||||
#include <FastLED.h>
|
||||
|
||||
#include "fl/downscale.h"
|
||||
#include "fl/draw_visitor.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/raster.h"
|
||||
|
|
@ -18,7 +24,6 @@ all the UI elements you see below.
|
|||
#include "fl/ui.h"
|
||||
#include "fl/xypath.h"
|
||||
#include "fx/time.h"
|
||||
#include "fl/bilinear_compression.h"
|
||||
|
||||
// Sketch.
|
||||
#include "src/wave.h"
|
||||
|
|
@ -32,10 +37,11 @@ using namespace fl;
|
|||
#define TIME_ANIMATION 1000 // ms
|
||||
|
||||
CRGB leds[NUM_LEDS];
|
||||
CRGB leds_downscaled[NUM_LEDS / 4]; // Downscaled buffer
|
||||
CRGB leds_downscaled[NUM_LEDS / 4]; // Downscaled buffer
|
||||
|
||||
XYMap xyMap(WIDTH, HEIGHT, false);
|
||||
XYMap xyMap_Dst(WIDTH / 2, HEIGHT / 2, false); // Framebuffer is regular rectangle LED matrix.
|
||||
XYMap xyMap_Dst(WIDTH / 2, HEIGHT / 2,
|
||||
false); // Framebuffer is regular rectangle LED matrix.
|
||||
// XYPathPtr shape = XYPath::NewRosePath(WIDTH, HEIGHT);
|
||||
|
||||
// Speed up writing to the super sampled waveFx by writing
|
||||
|
|
@ -44,7 +50,6 @@ XYMap xyMap_Dst(WIDTH / 2, HEIGHT / 2, false); // Framebuffer is regular rectang
|
|||
WaveEffect wave_fx; // init in setup().
|
||||
fl::vector<XYPathPtr> shapes = CreateXYPaths(WIDTH, HEIGHT);
|
||||
|
||||
|
||||
XYRaster raster(WIDTH, HEIGHT);
|
||||
TimeWarp time_warp;
|
||||
|
||||
|
|
@ -77,12 +82,12 @@ void setupUiCallbacks() {
|
|||
maxAnimation.onChanged(
|
||||
[](float value) { shapeProgress.set_max_clamp(maxAnimation.value()); });
|
||||
|
||||
trigger.onChanged([]() {
|
||||
trigger.onClicked([]() {
|
||||
// shapeProgress.trigger(millis());
|
||||
FASTLED_WARN("Trigger pressed");
|
||||
});
|
||||
useWaveFx.onChanged([](bool on) {
|
||||
if (on) {
|
||||
useWaveFx.onChanged([](fl::UICheckbox &checkbox) {
|
||||
if (checkbox.value()) {
|
||||
FASTLED_WARN("WaveFX enabled");
|
||||
} else {
|
||||
FASTLED_WARN("WaveFX disabled");
|
||||
|
|
@ -94,7 +99,8 @@ void setup() {
|
|||
Serial.begin(115200);
|
||||
auto screenmap = xyMap.toScreenMap();
|
||||
screenmap.setDiameter(.2);
|
||||
FastLED.addLeds<NEOPIXEL, 2>(leds, xyMap.getTotal()).setScreenMap(screenmap);
|
||||
FastLED.addLeds<NEOPIXEL, 2>(leds, xyMap.getTotal())
|
||||
.setScreenMap(screenmap);
|
||||
auto screenmap2 = xyMap_Dst.toScreenMap();
|
||||
screenmap.setDiameter(.5);
|
||||
screenmap2.addOffsetY(-HEIGHT / 2);
|
||||
|
|
@ -142,9 +148,6 @@ void loop() {
|
|||
s_prev_alpha = curr_alpha;
|
||||
}
|
||||
|
||||
const bool is_active =
|
||||
true || curr_alpha < maxAnimation.value() && curr_alpha > 0.0f;
|
||||
|
||||
static uint32_t frame = 0;
|
||||
frame++;
|
||||
clearLeds();
|
||||
|
|
@ -171,9 +174,6 @@ void loop() {
|
|||
}
|
||||
uint8_t alpha =
|
||||
fl::map_range<uint8_t>(i, 0.0f, number_of_steps - 1, 64, 255);
|
||||
if (!is_active) {
|
||||
alpha = 0;
|
||||
}
|
||||
Tile2x2_u8 subpixel = shape->at_subpixel(a);
|
||||
subpixel.scale(alpha);
|
||||
// subpixels.push_back(subpixel);
|
||||
|
|
@ -182,8 +182,7 @@ void loop() {
|
|||
|
||||
s_prev_alpha = curr_alpha;
|
||||
|
||||
|
||||
if (useWaveFx && is_active) {
|
||||
if (useWaveFx) {
|
||||
DrawRasterToWaveSimulator draw_wave_fx(&wave_fx);
|
||||
raster.draw(xyMap, draw_wave_fx);
|
||||
} else {
|
||||
14
libraries/FastLED/examples/Downscale/Downscale.ino
Normal file
14
libraries/FastLED/examples/Downscale/Downscale.ino
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
#include <FastLED.h>
|
||||
|
||||
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Platform does not have enough memory
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else
|
||||
|
||||
#include "Downscale.h"
|
||||
|
||||
#endif // SKETCH_HAS_LOTS_OF_MEMORY
|
||||
|
|
@ -22,7 +22,7 @@ struct WaveEffect {
|
|||
|
||||
struct DrawRasterToWaveSimulator {
|
||||
DrawRasterToWaveSimulator(WaveEffect* wave_fx) : mWaveFx(wave_fx) {}
|
||||
void draw(const vec2<int> &pt, uint32_t index, uint8_t value) {
|
||||
void draw(const vec2<int16_t> &pt, uint32_t index, uint8_t value) {
|
||||
float valuef = value / 255.0f;
|
||||
int xx = pt.x;
|
||||
int yy = pt.y;
|
||||
|
|
@ -3,60 +3,40 @@
|
|||
/// The Yves ESP32_S3 I2S driver is a driver that uses the I2S peripheral on the ESP32-S3 to drive leds.
|
||||
/// Originally from: https://github.com/hpwit/I2SClockLessLedDriveresp32s3
|
||||
///
|
||||
///
|
||||
/// This is an advanced driver. It has certain ramifications.
|
||||
/// - You probably aren't going to be able to use this in ArduinoIDE, because ArduinoIDE does not allow you to put in the necessary build flags.
|
||||
/// You will need to use PlatformIO to build this.
|
||||
/// - These flags enable PSRAM.
|
||||
/// - Once flashed, the ESP32-S3 might NOT want to be reprogrammed again. To get around
|
||||
/// this hold the reset button and release when the flash tool is looking for an
|
||||
/// an upload port.
|
||||
/// - Put a delay in the setup function. This is to make it easier to flash the device.
|
||||
/// - Put a delay in the setup function. This is to make it easier to flash the device during developement.
|
||||
/// - Serial output will mess up the DMA controller. I'm not sure why this is happening
|
||||
/// but just be aware of it. If your device suddenly stops works, remove the printfs and see if that fixes the problem.
|
||||
/// - You MUST use all the available PINS specified in this demo. Anything less than that will cause FastLED to crash.
|
||||
/// - Certain leds will turn white in debug mode. Probably has something to do with timing.
|
||||
/// but just be aware of it. If your device suddenly stops working, remove the printfs and see if that fixes the problem.
|
||||
///
|
||||
/// Is RGBW supported? Yes.
|
||||
///
|
||||
/// Is Overclocking supported? No.
|
||||
/// Is Overclocking supported? Yes. Use this to bend the timeings to support other WS281X variants. Fun fact, just overclock the
|
||||
/// chipset until the LED starts working.
|
||||
///
|
||||
/// What about the new WS2812-5VB leds? Kinda. We put in a hack to add the extra wait time of 300 uS.
|
||||
/// What about the new WS2812-5VB leds? Yes, they have 250us timing.
|
||||
///
|
||||
/// What are the advantages of using the FastLED bindings over the raw driver?
|
||||
/// - FastLED api is more user friendly since you don't have to combine all your leds into one rectangular block.
|
||||
/// - FastLED api allows you to have different sized strips which will be upscaled to the largest strip internally.
|
||||
///
|
||||
/// What are the advantages of using the raw driver over the FastLED bindings?
|
||||
/// - The raw driver uses less memory because it doesn't have a frame buffer copy.
|
||||
/// Why use this?
|
||||
/// Raw YVes driver needs a perfect parallel rectacngle buffer for operation. In this code we've provided FastLED
|
||||
/// type bindings.
|
||||
///
|
||||
// ArduinoIDE
|
||||
// Should already be enabled.
|
||||
//
|
||||
// PLATFORMIO BUILD FLAGS:
|
||||
// Define your platformio.ini like so:
|
||||
//
|
||||
// PlatformIO
|
||||
// [env:esp32s3]
|
||||
// platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip
|
||||
// platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip
|
||||
// framework = arduino
|
||||
// board = seeed_xiao_esp32s3
|
||||
// build_flags =
|
||||
// ${env:generic-esp.build_flags}
|
||||
// -DBOARD_HAS_PSRAM
|
||||
// -mfix-esp32-psram-cache-issue
|
||||
// -mfix-esp32-psram-cache-strategy=memw
|
||||
// board_build.partitions = huge_app.csv
|
||||
//
|
||||
// Then in your setup function you are going to want to call psramInit();
|
||||
//
|
||||
// Want to get a contributor badge for FastLED? This driver has only been lightly tested.
|
||||
// There are certain open questions:
|
||||
// - Can the pins order for the strips be changed? (the pins can be defined arbitrarily,Tested on esp32s3, esp32duino version 3.2.0)
|
||||
// - Are there some combination of pins that can be ommitted?
|
||||
// - What other caveats are there?
|
||||
//
|
||||
// If you know the answer to these questions then please submit a PR to the FastLED repo and
|
||||
// we will update the information for the community.
|
||||
|
||||
#include <esp_psram.h>
|
||||
|
||||
#define FASTLED_USES_ESP32S3_I2S
|
||||
#define FASTLED_USES_ESP32S3_I2S // Must define this before including FastLED.h
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "fl/assert.h"
|
||||
|
|
@ -66,6 +46,8 @@
|
|||
#define NUM_LEDS_PER_STRIP 256
|
||||
#define NUM_LEDS (NUM_LEDS_PER_STRIP * NUMSTRIPS)
|
||||
|
||||
// Note that you can use less strips than this.
|
||||
|
||||
#define EXAMPLE_PIN_NUM_DATA0 19 // B0
|
||||
#define EXAMPLE_PIN_NUM_DATA1 45 // B1
|
||||
#define EXAMPLE_PIN_NUM_DATA2 21 // B2
|
||||
|
|
@ -84,8 +66,9 @@
|
|||
#define EXAMPLE_PIN_NUM_DATA15 18 // R4
|
||||
|
||||
|
||||
const bool gUseFastLEDApi = true; // Set this to false to use the raw driver.
|
||||
|
||||
// Users say you can use a lot less strips. Experiment around and find out!
|
||||
// Please comment at reddit.com/r/fastled and let us know if you have problems.
|
||||
// Or send us a picture of your Triumps!
|
||||
int PINS[] = {
|
||||
EXAMPLE_PIN_NUM_DATA0,
|
||||
EXAMPLE_PIN_NUM_DATA1,
|
||||
|
|
@ -105,10 +88,9 @@ int PINS[] = {
|
|||
EXAMPLE_PIN_NUM_DATA15
|
||||
};
|
||||
|
||||
fl::InternalI2SDriver *driver = nullptr;
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
void setup_i2s_using_fastled_api() {
|
||||
void setup_i2s() {
|
||||
// Note, in this case we are using contingious memory for the leds. But this is not required.
|
||||
// Each strip can be a different size and the FastLED api will upscale the smaller strips to the largest strip.
|
||||
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA0, GRB>(
|
||||
|
|
@ -159,15 +141,13 @@ void setup_i2s_using_fastled_api() {
|
|||
FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA15, GRB>(
|
||||
leds + (15 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
|
||||
);
|
||||
FastLED.setBrightness(32);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
psramInit(); // IMPORTANT: This is required to enable PSRAM. If you don't do this, the driver will not work.
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
Serial.begin(57600);
|
||||
|
||||
// This is used so that you can see if PSRAM is enabled. If not, we will crash in setup() or in loop().
|
||||
log_d("Total heap: %d", ESP.getHeapSize());
|
||||
|
|
@ -175,15 +155,12 @@ void setup() {
|
|||
log_d("Total PSRAM: %d", ESP.getPsramSize()); // If this prints out 0, then PSRAM is not enabled.
|
||||
log_d("Free PSRAM: %d", ESP.getFreePsram());
|
||||
|
||||
log_d("waiting 6 second before startup");
|
||||
log_d("waiting 6 seconds before startup");
|
||||
delay(6000); // The long reset time here is to make it easier to flash the device during the development process.
|
||||
if (gUseFastLEDApi) {
|
||||
setup_i2s_using_fastled_api();
|
||||
} else {
|
||||
driver = fl::InternalI2SDriver::create();
|
||||
driver->initled((uint8_t *)leds, PINS, NUMSTRIPS, NUM_LEDS_PER_STRIP); // Skips extra frame buffer copy.
|
||||
driver->setBrightness(32);
|
||||
}
|
||||
|
||||
setup_i2s();
|
||||
FastLED.setBrightness(32);
|
||||
|
||||
}
|
||||
|
||||
void fill_rainbow(CRGB* all_leds) {
|
||||
|
|
@ -199,10 +176,6 @@ void fill_rainbow(CRGB* all_leds) {
|
|||
|
||||
void loop() {
|
||||
fill_rainbow(leds);
|
||||
if (gUseFastLEDApi) {
|
||||
FastLED.show();
|
||||
} else {
|
||||
FASTLED_ASSERT(driver != nullptr, "Did not expect driver to be null");
|
||||
driver->show();
|
||||
}
|
||||
FastLED.show();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,11 @@
|
|||
#include "old.h"
|
||||
#include "FastLED.h"
|
||||
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Platform does not have enough memory
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else
|
||||
|
||||
#include "curr.h"
|
||||
|
||||
#endif
|
||||
|
|
@ -1,112 +1,64 @@
|
|||
/*
|
||||
Festival Stick is a dense corkscrew of LEDs that is wrapped around one end of
|
||||
a wooden walking stick commonly found on amazon.A0
|
||||
Basic cork screw test.
|
||||
|
||||
The UI screenmap projects this cork screw into polar coordinates, so that the
|
||||
LEDs are mapped to a sprial, with the inner portion of the spiral being the top,
|
||||
the outer most portion being the bottom.
|
||||
This test is forward mapping, in which we test that
|
||||
the corkscrew is mapped to cylinder cartesian coordinates.
|
||||
|
||||
Most of the time, you'll want the reverse mapping, that is
|
||||
drawing to a rectangular grid, and then mapping that to a corkscrew.
|
||||
|
||||
However, to make sure the above mapping works correctly, we have
|
||||
to test that the forward mapping works correctly first.
|
||||
|
||||
*/
|
||||
|
||||
#include "fl/assert.h"
|
||||
#include "fl/corkscrew.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/sstream.h"
|
||||
#include "fl/grid.h"
|
||||
#include "fl/leds.h"
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/sstream.h"
|
||||
#include "fl/warn.h"
|
||||
#include "noise.h"
|
||||
#include <FastLED.h>
|
||||
// #include "vec3.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
// Power management settings
|
||||
#define VOLTS 5
|
||||
#define MAX_AMPS 1
|
||||
|
||||
#define PIN_DATA 9
|
||||
#define PIN_CLOCK 7
|
||||
#define PIN_DATA 3
|
||||
#define PIN_CLOCK 4
|
||||
|
||||
// Pin could have been tied to ground, instead it's tied to another pin.
|
||||
#define PIN_BUTTON 1
|
||||
#define PIN_GRND 2
|
||||
|
||||
#define NUM_LEDS 288
|
||||
#define CORKSCREW_TOTAL_LENGTH 100 // 100 cm
|
||||
#define CORKSCREW_TOTAL_HEIGHT \
|
||||
23.25f // Total height of the corkscrew in centimeters for 144 densly
|
||||
// wrapped up over 19 turns
|
||||
#define CORKSCREW_TURNS 19 // Default to 19 turns
|
||||
23.25 // when height = 0, it's a circle.
|
||||
// wrapped up over 19 turns
|
||||
#define CORKSCREW_TURNS 20.5 // Default to 19 turns
|
||||
|
||||
// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs
|
||||
// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter
|
||||
|
||||
#define CORKSCREW_WIDTH 16
|
||||
#define CORKSCREW_HEIGHT 19
|
||||
|
||||
|
||||
UITitle festivalStickTitle("Festival Stick");
|
||||
UITitle festivalStickTitle("Corkscrew");
|
||||
UIDescription festivalStickDescription(
|
||||
"Take a wooden walking stick, wrap dense LEDs around it like a corkscrew. "
|
||||
"Super simple but very awesome looking."
|
||||
"This assumes the dense 144 LEDs / meter.");
|
||||
"Tests the ability to map a cork screw onto a 2D cylindrical surface");
|
||||
|
||||
UISlider ledsScale("Leds scale", 0.1f, 0.1f, 1.0f, 0.01f);
|
||||
UIButton button("Button");
|
||||
UISlider speed("Speed", 0.1f, 0.01f, 1.0f, 0.01f);
|
||||
|
||||
CRGB leds[NUM_LEDS];
|
||||
UICheckbox allWhite("All White", false);
|
||||
UICheckbox splatRendering("Splat Rendering", true);
|
||||
|
||||
// CRGB leds[NUM_LEDS];
|
||||
|
||||
// Tested on a 288 led (2x 144 max density led strip) with 19 turns
|
||||
// with 23.25cm height, 19 turns, and ~15.5 LEDs per turn.
|
||||
Corkscrew::Input
|
||||
corkscrewInput(CORKSCREW_TOTAL_HEIGHT,
|
||||
CORKSCREW_TURNS * 2.0f * PI, // Default to 19 turns
|
||||
0, // offset to account for gaps between segments
|
||||
NUM_LEDS, // Default to dense 144 leds.
|
||||
);
|
||||
Corkscrew::Input corkscrewInput(CORKSCREW_TOTAL_LENGTH, CORKSCREW_TOTAL_HEIGHT,
|
||||
CORKSCREW_TURNS, NUM_LEDS, 0);
|
||||
|
||||
// Corkscrew::Output corkscrewMap = fl::Corkscrew::generateMap(corkscrewInput);
|
||||
// Corkscrew::State corkscrewMap = fl::Corkscrew::generateMap(corkscrewInput);
|
||||
Corkscrew corkscrew(corkscrewInput);
|
||||
|
||||
// Used only for the fl::ScreenMap generation.
|
||||
struct corkscrew_args {
|
||||
int num_leds = NUM_LEDS;
|
||||
float leds_per_turn = 15.5;
|
||||
float width_cm = 1.0;
|
||||
};
|
||||
|
||||
fl::ScreenMap makeScreenMap(corkscrew_args args = corkscrew_args()) {
|
||||
// Create a ScreenMap for the corkscrew
|
||||
fl::vector<vec2f> points(args.num_leds);
|
||||
|
||||
int num_leds = args.num_leds;
|
||||
float leds_per_turn = args.leds_per_turn;
|
||||
float width_cm = args.width_cm;
|
||||
|
||||
const float circumference = leds_per_turn;
|
||||
const float radius = circumference / (2.0 * PI); // radius in mm
|
||||
const float angle_per_led = 2.0 * PI / leds_per_turn; // degrees per LED
|
||||
const float height_per_turn_cm = width_cm; // 10cm height per turn
|
||||
const float height_per_led = height_per_turn_cm / leds_per_turn *
|
||||
1.3; // this is the changing height per led.
|
||||
|
||||
for (int i = 0; i < num_leds; i++) {
|
||||
float angle = i * angle_per_led; // angle in radians
|
||||
float r = radius + 10 + i * height_per_led; // height in cm
|
||||
|
||||
// Calculate the x, y coordinates for the corkscrew
|
||||
float x = r * cos(angle); // x coordinate
|
||||
float y = r * sin(angle); // y coordinate
|
||||
|
||||
// Store the 2D coordinates in the vector
|
||||
points[i] = vec2f(x, y);
|
||||
}
|
||||
|
||||
FASTLED_WARN("Creating ScreenMap with:\n" << points);
|
||||
|
||||
// Create a ScreenMap from the points
|
||||
fl::ScreenMap screenMap(points.data(), num_leds, .5);
|
||||
return screenMap;
|
||||
}
|
||||
|
||||
// Create a corkscrew with:
|
||||
// - 30cm total length (300mm)
|
||||
// - 5cm width (50mm)
|
||||
|
|
@ -117,96 +69,77 @@ fl::ScreenMap makeScreenMap(corkscrew_args args = corkscrew_args()) {
|
|||
|
||||
// fl::vector<vec3f> mapCorkScrew = makeCorkScrew(args);
|
||||
fl::ScreenMap screenMap;
|
||||
fl::Grid<CRGB> frameBuffer;
|
||||
|
||||
|
||||
CLEDController *addController() {
|
||||
CLEDController *controller =
|
||||
&FastLED.addLeds<APA102HD, PIN_DATA, PIN_CLOCK, BGR>(leds, NUM_LEDS);
|
||||
return controller;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pinMode(PIN_GRND, OUTPUT);
|
||||
digitalWrite(PIN_GRND, LOW); // Set ground pin to low
|
||||
button.addRealButton(Button(PIN_BUTTON));
|
||||
corkscrew_args args = corkscrew_args();
|
||||
screenMap = makeScreenMap(args);
|
||||
// screenMap = ScreenMap::Circle(NUM_LEDS, 1.5f, 0.5f, 1.0f);
|
||||
auto controller = addController();
|
||||
int width = corkscrew.cylinder_width();
|
||||
int height = corkscrew.cylinder_height();
|
||||
|
||||
frameBuffer.reset(width, height);
|
||||
XYMap xyMap = XYMap::constructRectangularGrid(width, height, 0);
|
||||
|
||||
CRGB *leds = frameBuffer.data();
|
||||
size_t num_leds = frameBuffer.size();
|
||||
|
||||
CLEDController* controller = &FastLED.addLeds<APA102HD, PIN_DATA, PIN_CLOCK, BGR>(leds, NUM_LEDS);
|
||||
|
||||
// CLEDController *controller =
|
||||
// &FastLED.addLeds<WS2812, 3, BGR>(leds, num_leds);
|
||||
|
||||
fl::ScreenMap screenMap = xyMap.toScreenMap();
|
||||
screenMap.setDiameter(.2f);
|
||||
|
||||
// Set the screen map for the controller
|
||||
controller->setScreenMap(screenMap);
|
||||
|
||||
// Set power management. This allows this festival stick to conformatable
|
||||
// run on any USB battery that can output at least 1A at 5V.
|
||||
// Keep in mind that this sketch is designed to use APA102HD mode, which
|
||||
// will result in even lowwer run power consumption, since the power mode
|
||||
// does not take into account the APA102HD gamma correction. However it is
|
||||
// still a correct upper bound that will match the ledset exactly when the
|
||||
// display tries to go full white.
|
||||
FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_AMPS * 1000);
|
||||
button.onChanged([](UIButton &but) {
|
||||
// This function is called when the button is pressed
|
||||
// If the button is pressed, show the generative pattern
|
||||
if (but.isPressed()) {
|
||||
FASTLED_WARN("Button pressed");
|
||||
} else {
|
||||
FASTLED_WARN("NOT Button pressed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void printOutput(const Corkscrew::Output& output) {
|
||||
fl::sstream stream;
|
||||
stream << "Corkscrew Output:\n";
|
||||
stream << "Width: " << output.width << "\n";
|
||||
stream << "Height: " << output.height << "\n";
|
||||
// stream << "Mapping: \n";
|
||||
// for (const auto &point : output.mapping) {
|
||||
// stream << point << "\n";
|
||||
// }
|
||||
FASTLED_WARN(stream.str());
|
||||
}
|
||||
|
||||
|
||||
|
||||
LedsXY<CORKSCREW_WIDTH, CORKSCREW_HEIGHT> frameBuffer;
|
||||
|
||||
void loop() {
|
||||
uint32_t now = millis();
|
||||
fl::clear(leds);
|
||||
// fl::clear(lesdds);
|
||||
fl::clear(frameBuffer);
|
||||
|
||||
static int w = 0;
|
||||
static float pos = 0;
|
||||
|
||||
EVERY_N_MILLIS(300) {
|
||||
// Update the corkscrew mapping every second
|
||||
w = (w + 1) % CORKSCREW_WIDTH;
|
||||
// Update the corkscrew mapping every second
|
||||
// w = (w + 1) % CORKSCREW_WIDTH;
|
||||
// frameBuffer.
|
||||
pos += speed.value();
|
||||
if (pos > corkscrew.size() - 1) {
|
||||
pos = 0; // Reset to the beginning
|
||||
}
|
||||
|
||||
|
||||
// draw a blue line down the middle
|
||||
for (int i = 0; i < CORKSCREW_HEIGHT; ++i) {
|
||||
frameBuffer.at(w % CORKSCREW_WIDTH, i) = CRGB::Blue;
|
||||
frameBuffer.at((w + 1) % CORKSCREW_WIDTH, i) = CRGB::Blue;
|
||||
frameBuffer.at((w - 1 + CORKSCREW_WIDTH) % CORKSCREW_WIDTH, i) = CRGB::Blue;
|
||||
frameBuffer.at((w + 2) % CORKSCREW_WIDTH, i) = CRGB::Blue;
|
||||
frameBuffer.at((w - 2 + CORKSCREW_WIDTH) % CORKSCREW_WIDTH, i) = CRGB::Blue;
|
||||
if (allWhite) {
|
||||
for (size_t i = 0; i < frameBuffer.size(); ++i) {
|
||||
frameBuffer.data()[i] = CRGB(8, 8, 8);
|
||||
}
|
||||
}
|
||||
|
||||
if (splatRendering) {
|
||||
Tile2x2_u8_wrap pos_tile = corkscrew.at_wrap(pos);
|
||||
const CRGB color = CRGB::Blue;
|
||||
// Draw each pixel in the 2x2 tile using the new wrapping API
|
||||
for (int dx = 0; dx < 2; ++dx) {
|
||||
for (int dy = 0; dy < 2; ++dy) {
|
||||
auto data = pos_tile.at(dx, dy);
|
||||
vec2i16 wrapped_pos = data.first; // Already wrapped position
|
||||
uint8_t alpha = data.second; // Alpha value
|
||||
|
||||
|
||||
// printOutput(corkscrewMap);
|
||||
|
||||
for (int i = 0; i < NUM_LEDS; ++i) {
|
||||
// Get the position in the frame buffer
|
||||
vec2<int16_t> pos = corkscrew.at(i);
|
||||
// Draw the tile to the frame buffer
|
||||
CRGB c = frameBuffer.at(pos.x, pos.y);
|
||||
leds[i] = c;
|
||||
|
||||
FASTLED_WARN_IF(i < 16, "LED " << i << " at position: "
|
||||
<< pos.x << ", " << pos.y
|
||||
<< " with color: " << c);
|
||||
|
||||
if (alpha > 0) { // Only draw if there's some alpha
|
||||
CRGB c = color;
|
||||
c.nscale8(alpha); // Scale the color by the alpha value
|
||||
frameBuffer.at(wrapped_pos.x, wrapped_pos.y) = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// None splat rendering, looks aweful.
|
||||
vec2f pos_vec2f = corkscrew.at_exact(pos);
|
||||
vec2i16 pos_i16 = vec2i16(round(pos_vec2f.x), round(pos_vec2f.y));
|
||||
// Now map the cork screw position to the cylindrical buffer that we
|
||||
// will draw.
|
||||
frameBuffer.at(pos_i16.x, pos_i16.y) =
|
||||
CRGB::Blue; // Draw a blue pixel at (w, h)
|
||||
}
|
||||
|
||||
FastLED.show();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ UIDescription festivalStickDescription(
|
|||
"Take a wooden walking stick, wrap dense LEDs around it like a corkscrew. Super simple but very awesome looking."
|
||||
"This assumes the dense 144 LEDs / meter.");
|
||||
|
||||
|
||||
|
||||
UISlider ledsScale("Leds scale", 0.1f, 0.1f, 1.0f, 0.01f);
|
||||
UIButton button("Button");
|
||||
|
||||
// Adding a brightness slider
|
||||
UISlider brightness("Brightness", 16, 0, 255, 1); // Brightness from 0 to 255
|
||||
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
|
|
@ -166,6 +166,8 @@ void setup() {
|
|||
// into account the APA102HD gamma correction. However it is still a correct upper bound
|
||||
// that will match the ledset exactly when the display tries to go full white.
|
||||
FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_AMPS * 1000);
|
||||
// set brightness 8
|
||||
FastLED.setBrightness(brightness.as_int());
|
||||
button.onChanged([](UIButton& but) {
|
||||
// This function is called when the button is pressed
|
||||
// If the button is pressed, show the generative pattern
|
||||
|
|
|
|||
214
libraries/FastLED/examples/FireCylinder/FireCylinder.h
Normal file
214
libraries/FastLED/examples/FireCylinder/FireCylinder.h
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
all the UI elements you see below.
|
||||
|
||||
OVERVIEW:
|
||||
This sketch creates a fire effect on a cylindrical LED display using Perlin noise.
|
||||
Unlike a flat matrix, this cylinder connects the left and right edges (x=0 and x=width-1),
|
||||
creating a seamless wrap-around effect. The fire appears to rise from the bottom,
|
||||
with colors transitioning from black to red/yellow/white (or other palettes).
|
||||
*/
|
||||
|
||||
// Perlin noise fire procedure
|
||||
// 16x16 rgb led cylinder demo
|
||||
// Exactly the same as the FireMatrix example, but with a cylinder, meaning that the x=0
|
||||
// and x = len-1 are connected.
|
||||
// This also showcases the inoise16(x,y,z,t) function which handles 3D+time noise effects.
|
||||
// Keep in mind that a cylinder is embedded in a 3D space with time being used to add
|
||||
// additional noise to the effect.
|
||||
|
||||
// HOW THE CYLINDRICAL FIRE EFFECT WORKS:
|
||||
// 1. We use sine and cosine to map the x-coordinate to a circle in 3D space
|
||||
// 2. This creates a cylindrical mapping where the left and right edges connect seamlessly
|
||||
// 3. We use 4D Perlin noise (x,y,z,t) to generate natural-looking fire patterns
|
||||
// 4. The height coordinate controls color fade-out to create the rising fire effect
|
||||
// 5. The time dimension adds continuous variation to make the fire look dynamic
|
||||
|
||||
#include "FastLED.h" // Main FastLED library for controlling LEDs
|
||||
#include "fl/ui.h" // UI components for the FastLED web compiler (sliders, buttons, etc.)
|
||||
#include "fl/xymap.h" // Mapping between 1D LED array and 2D coordinates
|
||||
#include "fx/time.h" // Time manipulation utilities for animations
|
||||
|
||||
using namespace fl; // Use the FastLED namespace for convenience
|
||||
|
||||
// Cylinder dimensions - this defines the size of our virtual LED grid
|
||||
#define HEIGHT 100 // Number of rows in the cylinder (vertical dimension)
|
||||
#define WIDTH 100 // Number of columns in the cylinder (circumference)
|
||||
#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts)
|
||||
#define BRIGHTNESS 255 // Maximum brightness level (0-255)
|
||||
|
||||
// UI elements that appear in the FastLED web compiler interface:
|
||||
UITitle title("FireCylinder Demo"); // Title displayed in the UI
|
||||
UIDescription description("This Fire demo wraps around the cylinder. It uses Perlin noise to create a fire effect.");
|
||||
|
||||
// TimeWarp helps control animation speed - it tracks time and allows speed adjustments
|
||||
TimeWarp timeScale(0, 1.0f); // Initialize with 0 starting time and 1.0 speed multiplier
|
||||
|
||||
// UI Controls for adjusting the fire effect:
|
||||
UISlider scaleXY("Scale", 8, 1, 100, 1); // Controls the overall size of the fire pattern
|
||||
UISlider speedY("SpeedY", 1.3, 1, 6, .1); // Controls how fast the fire moves upward
|
||||
UISlider scaleX("ScaleX", .3, 0.1, 3, .01); // Controls the horizontal scale (affects the wrap-around)
|
||||
UISlider invSpeedZ("Inverse SpeedZ", 20, 1, 100, 1); // Controls how fast the fire pattern changes over time (higher = slower)
|
||||
UISlider brightness("Brightness", 255, 0, 255, 1); // Controls overall brightness
|
||||
UINumberField palette("Palette", 0, 0, 2); // Selects which color palette to use (0=fire, 1=green, 2=blue)
|
||||
|
||||
// Array to hold all LED color values - one CRGB struct per LED
|
||||
CRGB leds[HEIGHT * WIDTH];
|
||||
|
||||
// Color palettes define the gradient of colors used for the fire effect
|
||||
// Each entry has the format: position (0-255), R, G, B
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(firepal){
|
||||
// Traditional fire palette - transitions from black to red to yellow to white
|
||||
0, 0, 0, 0, // black (bottom of fire)
|
||||
32, 255, 0, 0, // red (base of flames)
|
||||
190, 255, 255, 0, // yellow (middle of flames)
|
||||
255, 255, 255, 255 // white (hottest part/tips of flames)
|
||||
};
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
|
||||
// Green fire palette - for a toxic/alien look
|
||||
0, 0, 0, 0, // black (bottom)
|
||||
32, 0, 70, 0, // dark green (base)
|
||||
190, 57, 255, 20, // electric neon green (middle)
|
||||
255, 255, 255, 255 // white (hottest part)
|
||||
};
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
|
||||
// Blue fire palette - for a cold/ice fire look
|
||||
0, 0, 0, 0, // Black (bottom)
|
||||
32, 0, 0, 70, // Dark blue (base)
|
||||
128, 20, 57, 255, // Electric blue (middle)
|
||||
255, 255, 255, 255 // White (hottest part)
|
||||
};
|
||||
|
||||
// Create a mapping between 1D array positions and 2D x,y coordinates
|
||||
XYMap xyMap(WIDTH, HEIGHT, SERPENTINE);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200); // Initialize serial communication for debugging
|
||||
|
||||
// Initialize the LED strip:
|
||||
// - NEOPIXEL is the LED type
|
||||
// - 3 is the data pin number (for real hardware)
|
||||
// - setScreenMap connects our 2D coordinate system to the 1D LED array
|
||||
fl::ScreenMap screen_map = xyMap.toScreenMap();
|
||||
screen_map.setDiameter(0.1f); // Set the diameter for the cylinder (0.2 cm per LED)
|
||||
FastLED.addLeds<NEOPIXEL, 3>(leds, HEIGHT * WIDTH).setScreenMap(screen_map);
|
||||
|
||||
// Apply color correction for more accurate colors on LED strips
|
||||
FastLED.setCorrection(TypicalLEDStrip);
|
||||
}
|
||||
|
||||
uint8_t getPaletteIndex(uint32_t millis32, int width, int max_width, int height, int max_height,
|
||||
uint32_t y_speed) {
|
||||
// This function calculates which color to use from our palette for each LED
|
||||
|
||||
// Get the scale factor from the UI slider
|
||||
uint16_t scale = scaleXY.as<uint16_t>();
|
||||
|
||||
// Convert width position to an angle (0-255 represents 0-360 degrees)
|
||||
// This maps our flat coordinate to a position on a cylinder
|
||||
float xf = (float)width / (float)max_width; // Normalized position (0.0 to 1.0)
|
||||
uint8_t x = (uint8_t)(xf * 255); // Convert to 0-255 range for trig functions
|
||||
|
||||
// Calculate the sine and cosine of this angle to get 3D coordinates on the cylinder
|
||||
uint32_t cosx = cos8(x); // cos8 returns a value 0-255 representing cosine
|
||||
uint32_t sinx = sin8(x); // sin8 returns a value 0-255 representing sine
|
||||
|
||||
// Apply scaling to the sine/cosine values
|
||||
// This controls how "wide" the noise pattern is around the cylinder
|
||||
float trig_scale = scale * scaleX.value();
|
||||
cosx *= trig_scale;
|
||||
sinx *= trig_scale;
|
||||
|
||||
// Calculate Y coordinate (vertical position) with speed offset for movement
|
||||
uint32_t y = height * scale + y_speed;
|
||||
|
||||
// Calculate Z coordinate (time dimension) - controls how the pattern changes over time
|
||||
uint16_t z = millis32 / invSpeedZ.as<uint16_t>();
|
||||
|
||||
// Generate 16-bit Perlin noise using our 4D coordinates (x,y,z,t)
|
||||
// The << 8 shifts values left by 8 bits (multiplies by 256) to use the full 16-bit range
|
||||
// The last parameter (0) could be replaced with another time variable for more variation
|
||||
uint16_t noise16 = inoise16(cosx << 8, sinx << 8, y << 8, 0);
|
||||
|
||||
// Convert 16-bit noise to 8-bit by taking the high byte
|
||||
uint8_t noise_val = noise16 >> 8;
|
||||
|
||||
// Calculate how much to subtract based on vertical position (height)
|
||||
// This creates the fade-out effect from bottom to top
|
||||
// The formula maps height from 0 to max_height-1 to a value from 255 to 0
|
||||
int8_t subtraction_factor = abs8(height - (max_height - 1)) * 255 /
|
||||
(max_height - 1);
|
||||
|
||||
// Subtract the factor from the noise value (with underflow protection)
|
||||
// qsub8 is a "saturating subtraction" - it won't go below 0
|
||||
return qsub8(noise_val, subtraction_factor);
|
||||
}
|
||||
CRGBPalette16 getPalette() {
|
||||
// This function returns the appropriate color palette based on the UI selection
|
||||
switch (palette) {
|
||||
case 0:
|
||||
return firepal; // Traditional orange/red fire
|
||||
case 1:
|
||||
return electricGreenFirePal; // Green "toxic" fire
|
||||
case 2:
|
||||
return electricBlueFirePal; // Blue "cold" fire
|
||||
default:
|
||||
return firepal; // Default to traditional fire if invalid value
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// The main program loop that runs continuously
|
||||
|
||||
// Set the overall brightness from the UI slider
|
||||
FastLED.setBrightness(brightness);
|
||||
|
||||
// Get the selected color palette
|
||||
CRGBPalette16 myPal = getPalette();
|
||||
|
||||
// Get the current time in milliseconds
|
||||
uint32_t now = millis();
|
||||
|
||||
// Update the animation speed from the UI slider
|
||||
timeScale.setSpeed(speedY);
|
||||
|
||||
// Calculate the current y-offset for animation (makes the fire move)
|
||||
uint32_t y_speed = timeScale.update(now);
|
||||
|
||||
// Loop through every LED in our cylindrical matrix
|
||||
for (int width = 0; width < WIDTH; width++) {
|
||||
for (int height = 0; height < HEIGHT; height++) {
|
||||
// Calculate which color to use from our palette for this LED
|
||||
// This function handles the cylindrical mapping using sine/cosine
|
||||
uint8_t palette_index =
|
||||
getPaletteIndex(now, width, WIDTH, height, HEIGHT, y_speed);
|
||||
|
||||
// Get the actual RGB color from the palette
|
||||
// BRIGHTNESS ensures we use the full brightness range
|
||||
CRGB c = ColorFromPalette(myPal, palette_index, BRIGHTNESS);
|
||||
|
||||
// Convert our 2D coordinates to the 1D array index
|
||||
// We use (WIDTH-1)-width and (HEIGHT-1)-height to flip the coordinates
|
||||
// This makes the fire appear to rise from the bottom
|
||||
int index = xyMap((WIDTH - 1) - width, (HEIGHT - 1) - height);
|
||||
|
||||
// Set the LED color in our array
|
||||
leds[index] = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the color data to the actual LEDs
|
||||
FastLED.show();
|
||||
}
|
||||
|
|
@ -1,207 +1,9 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
all the UI elements you see below.
|
||||
|
||||
OVERVIEW:
|
||||
This sketch creates a fire effect on a cylindrical LED display using Perlin noise.
|
||||
Unlike a flat matrix, this cylinder connects the left and right edges (x=0 and x=width-1),
|
||||
creating a seamless wrap-around effect. The fire appears to rise from the bottom,
|
||||
with colors transitioning from black to red/yellow/white (or other palettes).
|
||||
*/
|
||||
|
||||
// Perlin noise fire procedure
|
||||
// 16x16 rgb led cylinder demo
|
||||
// Exactly the same as the FireMatrix example, but with a cylinder, meaning that the x=0
|
||||
// and x = len-1 are connected.
|
||||
// This also showcases the inoise16(x,y,z,t) function which handles 3D+time noise effects.
|
||||
// Keep in mind that a cylinder is embedded in a 3D space with time being used to add
|
||||
// additional noise to the effect.
|
||||
|
||||
// HOW THE CYLINDRICAL FIRE EFFECT WORKS:
|
||||
// 1. We use sine and cosine to map the x-coordinate to a circle in 3D space
|
||||
// 2. This creates a cylindrical mapping where the left and right edges connect seamlessly
|
||||
// 3. We use 4D Perlin noise (x,y,z,t) to generate natural-looking fire patterns
|
||||
// 4. The height coordinate controls color fade-out to create the rising fire effect
|
||||
// 5. The time dimension adds continuous variation to make the fire look dynamic
|
||||
|
||||
#include "FastLED.h" // Main FastLED library for controlling LEDs
|
||||
#include "fl/ui.h" // UI components for the FastLED web compiler (sliders, buttons, etc.)
|
||||
#include "fl/xymap.h" // Mapping between 1D LED array and 2D coordinates
|
||||
#include "fx/time.h" // Time manipulation utilities for animations
|
||||
|
||||
using namespace fl; // Use the FastLED namespace for convenience
|
||||
|
||||
// Cylinder dimensions - this defines the size of our virtual LED grid
|
||||
#define HEIGHT 100 // Number of rows in the cylinder (vertical dimension)
|
||||
#define WIDTH 100 // Number of columns in the cylinder (circumference)
|
||||
#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts)
|
||||
#define BRIGHTNESS 255 // Maximum brightness level (0-255)
|
||||
|
||||
// UI elements that appear in the FastLED web compiler interface:
|
||||
UITitle title("FireCylinder Demo"); // Title displayed in the UI
|
||||
UIDescription description("This Fire demo wraps around the cylinder. It uses Perlin noise to create a fire effect.");
|
||||
|
||||
// TimeWarp helps control animation speed - it tracks time and allows speed adjustments
|
||||
TimeWarp timeScale(0, 1.0f); // Initialize with 0 starting time and 1.0 speed multiplier
|
||||
|
||||
// UI Controls for adjusting the fire effect:
|
||||
UISlider scaleXY("Scale", 8, 1, 100, 1); // Controls the overall size of the fire pattern
|
||||
UISlider speedY("SpeedY", 1.3, 1, 6, .1); // Controls how fast the fire moves upward
|
||||
UISlider scaleX("ScaleX", .3, 0.1, 3, .01); // Controls the horizontal scale (affects the wrap-around)
|
||||
UISlider invSpeedZ("Inverse SpeedZ", 20, 1, 100, 1); // Controls how fast the fire pattern changes over time (higher = slower)
|
||||
UISlider brightness("Brightness", 255, 0, 255, 1); // Controls overall brightness
|
||||
UINumberField palette("Palette", 0, 0, 2); // Selects which color palette to use (0=fire, 1=green, 2=blue)
|
||||
|
||||
// Array to hold all LED color values - one CRGB struct per LED
|
||||
CRGB leds[HEIGHT * WIDTH];
|
||||
|
||||
// Color palettes define the gradient of colors used for the fire effect
|
||||
// Each entry has the format: position (0-255), R, G, B
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(firepal){
|
||||
// Traditional fire palette - transitions from black to red to yellow to white
|
||||
0, 0, 0, 0, // black (bottom of fire)
|
||||
32, 255, 0, 0, // red (base of flames)
|
||||
190, 255, 255, 0, // yellow (middle of flames)
|
||||
255, 255, 255, 255 // white (hottest part/tips of flames)
|
||||
};
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
|
||||
// Green fire palette - for a toxic/alien look
|
||||
0, 0, 0, 0, // black (bottom)
|
||||
32, 0, 70, 0, // dark green (base)
|
||||
190, 57, 255, 20, // electric neon green (middle)
|
||||
255, 255, 255, 255 // white (hottest part)
|
||||
};
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
|
||||
// Blue fire palette - for a cold/ice fire look
|
||||
0, 0, 0, 0, // Black (bottom)
|
||||
32, 0, 0, 70, // Dark blue (base)
|
||||
128, 20, 57, 255, // Electric blue (middle)
|
||||
255, 255, 255, 255 // White (hottest part)
|
||||
};
|
||||
|
||||
// Create a mapping between 1D array positions and 2D x,y coordinates
|
||||
XYMap xyMap(HEIGHT, WIDTH, SERPENTINE);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200); // Initialize serial communication for debugging
|
||||
|
||||
// Initialize the LED strip:
|
||||
// - NEOPIXEL is the LED type
|
||||
// - 3 is the data pin number (for real hardware)
|
||||
// - setScreenMap connects our 2D coordinate system to the 1D LED array
|
||||
FastLED.addLeds<NEOPIXEL, 3>(leds, HEIGHT * WIDTH).setScreenMap(xyMap);
|
||||
|
||||
// Apply color correction for more accurate colors on LED strips
|
||||
FastLED.setCorrection(TypicalLEDStrip);
|
||||
}
|
||||
|
||||
uint8_t getPaletteIndex(uint32_t millis32, int width, int max_width, int height, int max_height,
|
||||
uint32_t y_speed) {
|
||||
// This function calculates which color to use from our palette for each LED
|
||||
|
||||
// Get the scale factor from the UI slider
|
||||
uint16_t scale = scaleXY.as<uint16_t>();
|
||||
|
||||
// Convert width position to an angle (0-255 represents 0-360 degrees)
|
||||
// This maps our flat coordinate to a position on a cylinder
|
||||
float xf = (float)width / (float)max_width; // Normalized position (0.0 to 1.0)
|
||||
uint8_t x = (uint8_t)(xf * 255); // Convert to 0-255 range for trig functions
|
||||
|
||||
// Calculate the sine and cosine of this angle to get 3D coordinates on the cylinder
|
||||
uint32_t cosx = cos8(x); // cos8 returns a value 0-255 representing cosine
|
||||
uint32_t sinx = sin8(x); // sin8 returns a value 0-255 representing sine
|
||||
|
||||
// Apply scaling to the sine/cosine values
|
||||
// This controls how "wide" the noise pattern is around the cylinder
|
||||
float trig_scale = scale * scaleX.value();
|
||||
cosx *= trig_scale;
|
||||
sinx *= trig_scale;
|
||||
|
||||
// Calculate Y coordinate (vertical position) with speed offset for movement
|
||||
uint32_t y = height * scale + y_speed;
|
||||
|
||||
// Calculate Z coordinate (time dimension) - controls how the pattern changes over time
|
||||
uint16_t z = millis32 / invSpeedZ.as<uint16_t>();
|
||||
|
||||
// Generate 16-bit Perlin noise using our 4D coordinates (x,y,z,t)
|
||||
// The << 8 shifts values left by 8 bits (multiplies by 256) to use the full 16-bit range
|
||||
// The last parameter (0) could be replaced with another time variable for more variation
|
||||
uint16_t noise16 = inoise16(cosx << 8, sinx << 8, y << 8, 0);
|
||||
|
||||
// Convert 16-bit noise to 8-bit by taking the high byte
|
||||
uint8_t noise_val = noise16 >> 8;
|
||||
|
||||
// Calculate how much to subtract based on vertical position (height)
|
||||
// This creates the fade-out effect from bottom to top
|
||||
// The formula maps height from 0 to max_height-1 to a value from 255 to 0
|
||||
int8_t subtraction_factor = abs8(height - (max_height - 1)) * 255 /
|
||||
(max_height - 1);
|
||||
|
||||
// Subtract the factor from the noise value (with underflow protection)
|
||||
// qsub8 is a "saturating subtraction" - it won't go below 0
|
||||
return qsub8(noise_val, subtraction_factor);
|
||||
}
|
||||
CRGBPalette16 getPalette() {
|
||||
// This function returns the appropriate color palette based on the UI selection
|
||||
switch (palette) {
|
||||
case 0:
|
||||
return firepal; // Traditional orange/red fire
|
||||
case 1:
|
||||
return electricGreenFirePal; // Green "toxic" fire
|
||||
case 2:
|
||||
return electricBlueFirePal; // Blue "cold" fire
|
||||
default:
|
||||
return firepal; // Default to traditional fire if invalid value
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// The main program loop that runs continuously
|
||||
|
||||
// Set the overall brightness from the UI slider
|
||||
FastLED.setBrightness(brightness);
|
||||
|
||||
// Get the selected color palette
|
||||
CRGBPalette16 myPal = getPalette();
|
||||
|
||||
// Get the current time in milliseconds
|
||||
uint32_t now = millis();
|
||||
|
||||
// Update the animation speed from the UI slider
|
||||
timeScale.setSpeed(speedY);
|
||||
|
||||
// Calculate the current y-offset for animation (makes the fire move)
|
||||
uint32_t y_speed = timeScale.update(now);
|
||||
|
||||
// Loop through every LED in our cylindrical matrix
|
||||
for (int width = 0; width < WIDTH; width++) {
|
||||
for (int height = 0; height < HEIGHT; height++) {
|
||||
// Calculate which color to use from our palette for this LED
|
||||
// This function handles the cylindrical mapping using sine/cosine
|
||||
uint8_t palette_index =
|
||||
getPaletteIndex(now, width, WIDTH, height, HEIGHT, y_speed);
|
||||
|
||||
// Get the actual RGB color from the palette
|
||||
// BRIGHTNESS ensures we use the full brightness range
|
||||
CRGB c = ColorFromPalette(myPal, palette_index, BRIGHTNESS);
|
||||
|
||||
// Convert our 2D coordinates to the 1D array index
|
||||
// We use (WIDTH-1)-width and (HEIGHT-1)-height to flip the coordinates
|
||||
// This makes the fire appear to rise from the bottom
|
||||
int index = xyMap((WIDTH - 1) - width, (HEIGHT - 1) - height);
|
||||
|
||||
// Set the LED color in our array
|
||||
leds[index] = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the color data to the actual LEDs
|
||||
FastLED.show();
|
||||
}
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Platform does not have enough memory
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else
|
||||
#include "FireCylinder.h"
|
||||
#endif
|
||||
|
|
|
|||
200
libraries/FastLED/examples/FireMatrix/FireMatrix.h
Normal file
200
libraries/FastLED/examples/FireMatrix/FireMatrix.h
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
|
||||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
all the UI elements you see below.
|
||||
|
||||
OVERVIEW:
|
||||
This sketch creates a fire effect using Perlin noise on a matrix of LEDs.
|
||||
The fire appears to move upward with colors transitioning from black at the bottom
|
||||
to white at the top, with red and yellow in between (for the default palette).
|
||||
*/
|
||||
|
||||
// Perlin noise fire procedure
|
||||
// 16x16 rgb led matrix demo
|
||||
// Yaroslaw Turbin, 22.06.2020
|
||||
// https://vk.com/ldirko
|
||||
// https://www.reddit.com/user/ldirko/
|
||||
// https://www.reddit.com/r/FastLED/comments/hgu16i/my_fire_effect_implementation_based_on_perlin/
|
||||
// Based on the code found at: https://editor.soulmatelights.com/gallery/1229-
|
||||
|
||||
// HOW THE FIRE EFFECT WORKS:
|
||||
// 1. We use Perlin noise with time offset for X and Z coordinates
|
||||
// to create a naturally scrolling fire pattern that changes over time
|
||||
// 2. We distort the fire noise to make it look more realistic
|
||||
// 3. We subtract a value based on Y coordinate to shift the fire color in the palette
|
||||
// (not just brightness). This creates a fade-out effect from the bottom of the matrix to the top
|
||||
// 4. The palette is carefully designed to give realistic fire colors
|
||||
|
||||
#if defined(__AVR__)
|
||||
// Platform does not have enough memory
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else
|
||||
|
||||
#include "FastLED.h" // Main FastLED library for controlling LEDs
|
||||
#include "fl/ui.h" // UI components for the FastLED web compiler (sliders, etc.)
|
||||
#include "fl/xymap.h" // Mapping between 1D LED array and 2D coordinates
|
||||
#include "fx/time.h" // Time manipulation utilities
|
||||
|
||||
using namespace fl; // Use the FastLED namespace for convenience
|
||||
|
||||
// Matrix dimensions - this defines the size of our virtual LED grid
|
||||
#define HEIGHT 100 // Number of rows in the matrix
|
||||
#define WIDTH 100 // Number of columns in the matrix
|
||||
#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts)
|
||||
#define BRIGHTNESS 255 // Maximum brightness level (0-255)
|
||||
|
||||
// TimeWarp helps control animation speed - it tracks time and allows speed adjustments
|
||||
TimeWarp timeScale(0, 1.0f); // Initialize with 0 starting time and 1.0 speed multiplier
|
||||
|
||||
// UI Controls that appear in the FastLED web compiler interface:
|
||||
UISlider scaleXY("Scale", 20, 1, 100, 1); // Controls the size of the fire pattern
|
||||
UISlider speedY("SpeedY", 1, 1, 6, .1); // Controls how fast the fire moves upward
|
||||
UISlider invSpeedZ("Inverse SpeedZ", 20, 1, 100, 1); // Controls how fast the fire pattern changes over time (higher = slower)
|
||||
UISlider brightness("Brightness", 255, 0, 255, 1); // Controls overall brightness
|
||||
UINumberField palette("Palette", 0, 0, 2); // Selects which color palette to use (0=fire, 1=green, 2=blue)
|
||||
|
||||
// Array to hold all LED color values - one CRGB struct per LED
|
||||
CRGB leds[HEIGHT * WIDTH];
|
||||
|
||||
// Color palettes define the gradient of colors used for the fire effect
|
||||
// Each entry has the format: position (0-255), R, G, B
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(firepal){
|
||||
// Traditional fire palette - transitions from black to red to yellow to white
|
||||
0, 0, 0, 0, // black (bottom of fire)
|
||||
32, 255, 0, 0, // red (base of flames)
|
||||
190, 255, 255, 0, // yellow (middle of flames)
|
||||
255, 255, 255, 255 // white (hottest part/tips of flames)
|
||||
};
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
|
||||
// Green fire palette - for a toxic/alien look
|
||||
0, 0, 0, 0, // black (bottom)
|
||||
32, 0, 70, 0, // dark green (base)
|
||||
190, 57, 255, 20, // electric neon green (middle)
|
||||
255, 255, 255, 255 // white (hottest part)
|
||||
};
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(electricBlueFirePal) {
|
||||
// Blue fire palette - for a cold/ice fire look
|
||||
0, 0, 0, 0, // Black (bottom)
|
||||
32, 0, 0, 70, // Dark blue (base)
|
||||
128, 20, 57, 255, // Electric blue (middle)
|
||||
255, 255, 255, 255 // White (hottest part)
|
||||
};
|
||||
|
||||
// Create a mapping between 1D array positions and 2D x,y coordinates
|
||||
XYMap xyMap(WIDTH, HEIGHT, SERPENTINE);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200); // Initialize serial communication for debugging
|
||||
|
||||
// Initialize the LED strip:
|
||||
// - NEOPIXEL is the LED type
|
||||
// - 3 is the data pin number (for real hardware)
|
||||
// - setScreenMap connects our 2D coordinate system to the 1D LED array
|
||||
|
||||
fl::ScreenMap screen_map = xyMap.toScreenMap();
|
||||
screen_map.setDiameter(0.1f); // Set the diameter for the cylinder (0.2 cm per LED)
|
||||
FastLED.addLeds<NEOPIXEL, 3>(leds, HEIGHT * WIDTH).setScreenMap(screen_map);
|
||||
|
||||
// Apply color correction for more accurate colors on LED strips
|
||||
FastLED.setCorrection(TypicalLEDStrip);
|
||||
}
|
||||
|
||||
uint8_t getPaletteIndex(uint32_t millis32, int i, int j, uint32_t y_speed) {
|
||||
// This function calculates which color to use from our palette for each LED
|
||||
|
||||
// Get the scale factor from the UI slider (controls the "size" of the fire)
|
||||
uint16_t scale = scaleXY.as<uint16_t>();
|
||||
|
||||
// Calculate 3D coordinates for the Perlin noise function:
|
||||
uint16_t x = i * scale; // X position (horizontal in matrix)
|
||||
uint32_t y = j * scale + y_speed; // Y position (vertical) + movement offset
|
||||
uint16_t z = millis32 / invSpeedZ.as<uint16_t>(); // Z position (time dimension)
|
||||
|
||||
// Generate 16-bit Perlin noise value using these coordinates
|
||||
// The << 8 shifts values left by 8 bits (multiplies by 256) to use the full 16-bit range
|
||||
uint16_t noise16 = inoise16(x << 8, y << 8, z << 8);
|
||||
|
||||
// Convert 16-bit noise to 8-bit by taking the high byte (>> 8 shifts right by 8 bits)
|
||||
uint8_t noise_val = noise16 >> 8;
|
||||
|
||||
// Calculate how much to subtract based on vertical position (j)
|
||||
// This creates the fade-out effect from bottom to top
|
||||
// abs8() ensures we get a positive value
|
||||
// The formula maps j from 0 to HEIGHT-1 to a value from 255 to 0
|
||||
int8_t subtraction_factor = abs8(j - (HEIGHT - 1)) * 255 / (HEIGHT - 1);
|
||||
|
||||
// Subtract the factor from the noise value (with underflow protection)
|
||||
// qsub8 is a "saturating subtraction" - it won't go below 0
|
||||
return qsub8(noise_val, subtraction_factor);
|
||||
}
|
||||
|
||||
CRGBPalette16 getPalette() {
|
||||
// This function returns the appropriate color palette based on the UI selection
|
||||
switch (palette) {
|
||||
case 0:
|
||||
return firepal; // Traditional orange/red fire
|
||||
case 1:
|
||||
return electricGreenFirePal; // Green "toxic" fire
|
||||
case 2:
|
||||
return electricBlueFirePal; // Blue "cold" fire
|
||||
default:
|
||||
return firepal; // Default to traditional fire if invalid value
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// The main program loop that runs continuously
|
||||
|
||||
// Set the overall brightness from the UI slider
|
||||
FastLED.setBrightness(brightness);
|
||||
|
||||
// Get the selected color palette
|
||||
CRGBPalette16 myPal = getPalette();
|
||||
|
||||
// Get the current time in milliseconds
|
||||
uint32_t now = millis();
|
||||
|
||||
// Update the animation speed from the UI slider
|
||||
timeScale.setSpeed(speedY);
|
||||
|
||||
// Calculate the current y-offset for animation (makes the fire move)
|
||||
uint32_t y_speed = timeScale.update(now);
|
||||
|
||||
// Loop through every LED in our matrix
|
||||
for (int i = 0; i < WIDTH; i++) {
|
||||
for (int j = 0; j < HEIGHT; j++) {
|
||||
// Calculate which color to use from our palette for this LED
|
||||
uint8_t palette_index = getPaletteIndex(now, i, j, y_speed);
|
||||
|
||||
// Get the actual RGB color from the palette
|
||||
// BRIGHTNESS ensures we use the full brightness range
|
||||
CRGB c = ColorFromPalette(myPal, palette_index, BRIGHTNESS);
|
||||
|
||||
// Convert our 2D coordinates (i,j) to the 1D array index
|
||||
// We use (WIDTH-1)-i and (HEIGHT-1)-j to flip the coordinates
|
||||
// This makes the fire appear to rise from the bottom
|
||||
int index = xyMap((WIDTH - 1) - i, (HEIGHT - 1) - j);
|
||||
|
||||
// Set the LED color in our array
|
||||
leds[index] = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the color data to the actual LEDs
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,184 +1,9 @@
|
|||
|
||||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
all the UI elements you see below.
|
||||
|
||||
OVERVIEW:
|
||||
This sketch creates a fire effect using Perlin noise on a matrix of LEDs.
|
||||
The fire appears to move upward with colors transitioning from black at the bottom
|
||||
to white at the top, with red and yellow in between (for the default palette).
|
||||
*/
|
||||
|
||||
// Perlin noise fire procedure
|
||||
// 16x16 rgb led matrix demo
|
||||
// Yaroslaw Turbin, 22.06.2020
|
||||
// https://vk.com/ldirko
|
||||
// https://www.reddit.com/user/ldirko/
|
||||
// https://www.reddit.com/r/FastLED/comments/hgu16i/my_fire_effect_implementation_based_on_perlin/
|
||||
// Based on the code found at: https://editor.soulmatelights.com/gallery/1229-
|
||||
|
||||
// HOW THE FIRE EFFECT WORKS:
|
||||
// 1. We use Perlin noise with time offset for X and Z coordinates
|
||||
// to create a naturally scrolling fire pattern that changes over time
|
||||
// 2. We distort the fire noise to make it look more realistic
|
||||
// 3. We subtract a value based on Y coordinate to shift the fire color in the palette
|
||||
// (not just brightness). This creates a fade-out effect from the bottom of the matrix to the top
|
||||
// 4. The palette is carefully designed to give realistic fire colors
|
||||
|
||||
#include "FastLED.h" // Main FastLED library for controlling LEDs
|
||||
#include "fl/ui.h" // UI components for the FastLED web compiler (sliders, etc.)
|
||||
#include "fl/xymap.h" // Mapping between 1D LED array and 2D coordinates
|
||||
#include "fx/time.h" // Time manipulation utilities
|
||||
|
||||
using namespace fl; // Use the FastLED namespace for convenience
|
||||
|
||||
// Matrix dimensions - this defines the size of our virtual LED grid
|
||||
#define HEIGHT 100 // Number of rows in the matrix
|
||||
#define WIDTH 100 // Number of columns in the matrix
|
||||
#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts)
|
||||
#define BRIGHTNESS 255 // Maximum brightness level (0-255)
|
||||
|
||||
// TimeWarp helps control animation speed - it tracks time and allows speed adjustments
|
||||
TimeWarp timeScale(0, 1.0f); // Initialize with 0 starting time and 1.0 speed multiplier
|
||||
|
||||
// UI Controls that appear in the FastLED web compiler interface:
|
||||
UISlider scaleXY("Scale", 20, 1, 100, 1); // Controls the size of the fire pattern
|
||||
UISlider speedY("SpeedY", 1, 1, 6, .1); // Controls how fast the fire moves upward
|
||||
UISlider invSpeedZ("Inverse SpeedZ", 20, 1, 100, 1); // Controls how fast the fire pattern changes over time (higher = slower)
|
||||
UISlider brightness("Brightness", 255, 0, 255, 1); // Controls overall brightness
|
||||
UINumberField palette("Palette", 0, 0, 2); // Selects which color palette to use (0=fire, 1=green, 2=blue)
|
||||
|
||||
// Array to hold all LED color values - one CRGB struct per LED
|
||||
CRGB leds[HEIGHT * WIDTH];
|
||||
|
||||
// Color palettes define the gradient of colors used for the fire effect
|
||||
// Each entry has the format: position (0-255), R, G, B
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(firepal){
|
||||
// Traditional fire palette - transitions from black to red to yellow to white
|
||||
0, 0, 0, 0, // black (bottom of fire)
|
||||
32, 255, 0, 0, // red (base of flames)
|
||||
190, 255, 255, 0, // yellow (middle of flames)
|
||||
255, 255, 255, 255 // white (hottest part/tips of flames)
|
||||
};
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
|
||||
// Green fire palette - for a toxic/alien look
|
||||
0, 0, 0, 0, // black (bottom)
|
||||
32, 0, 70, 0, // dark green (base)
|
||||
190, 57, 255, 20, // electric neon green (middle)
|
||||
255, 255, 255, 255 // white (hottest part)
|
||||
};
|
||||
|
||||
DEFINE_GRADIENT_PALETTE(electricBlueFirePal) {
|
||||
// Blue fire palette - for a cold/ice fire look
|
||||
0, 0, 0, 0, // Black (bottom)
|
||||
32, 0, 0, 70, // Dark blue (base)
|
||||
128, 20, 57, 255, // Electric blue (middle)
|
||||
255, 255, 255, 255 // White (hottest part)
|
||||
};
|
||||
|
||||
// Create a mapping between 1D array positions and 2D x,y coordinates
|
||||
XYMap xyMap(HEIGHT, WIDTH, SERPENTINE);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200); // Initialize serial communication for debugging
|
||||
|
||||
// Initialize the LED strip:
|
||||
// - NEOPIXEL is the LED type
|
||||
// - 3 is the data pin number (for real hardware)
|
||||
// - setScreenMap connects our 2D coordinate system to the 1D LED array
|
||||
FastLED.addLeds<NEOPIXEL, 3>(leds, HEIGHT * WIDTH).setScreenMap(xyMap);
|
||||
|
||||
// Apply color correction for more accurate colors on LED strips
|
||||
FastLED.setCorrection(TypicalLEDStrip);
|
||||
}
|
||||
|
||||
uint8_t getPaletteIndex(uint32_t millis32, int i, int j, uint32_t y_speed) {
|
||||
// This function calculates which color to use from our palette for each LED
|
||||
|
||||
// Get the scale factor from the UI slider (controls the "size" of the fire)
|
||||
uint16_t scale = scaleXY.as<uint16_t>();
|
||||
|
||||
// Calculate 3D coordinates for the Perlin noise function:
|
||||
uint16_t x = i * scale; // X position (horizontal in matrix)
|
||||
uint32_t y = j * scale + y_speed; // Y position (vertical) + movement offset
|
||||
uint16_t z = millis32 / invSpeedZ.as<uint16_t>(); // Z position (time dimension)
|
||||
|
||||
// Generate 16-bit Perlin noise value using these coordinates
|
||||
// The << 8 shifts values left by 8 bits (multiplies by 256) to use the full 16-bit range
|
||||
uint16_t noise16 = inoise16(x << 8, y << 8, z << 8);
|
||||
|
||||
// Convert 16-bit noise to 8-bit by taking the high byte (>> 8 shifts right by 8 bits)
|
||||
uint8_t noise_val = noise16 >> 8;
|
||||
|
||||
// Calculate how much to subtract based on vertical position (j)
|
||||
// This creates the fade-out effect from bottom to top
|
||||
// abs8() ensures we get a positive value
|
||||
// The formula maps j from 0 to WIDTH-1 to a value from 255 to 0
|
||||
int8_t subtraction_factor = abs8(j - (WIDTH - 1)) * 255 / (WIDTH - 1);
|
||||
|
||||
// Subtract the factor from the noise value (with underflow protection)
|
||||
// qsub8 is a "saturating subtraction" - it won't go below 0
|
||||
return qsub8(noise_val, subtraction_factor);
|
||||
}
|
||||
|
||||
CRGBPalette16 getPalette() {
|
||||
// This function returns the appropriate color palette based on the UI selection
|
||||
switch (palette) {
|
||||
case 0:
|
||||
return firepal; // Traditional orange/red fire
|
||||
case 1:
|
||||
return electricGreenFirePal; // Green "toxic" fire
|
||||
case 2:
|
||||
return electricBlueFirePal; // Blue "cold" fire
|
||||
default:
|
||||
return firepal; // Default to traditional fire if invalid value
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// The main program loop that runs continuously
|
||||
|
||||
// Set the overall brightness from the UI slider
|
||||
FastLED.setBrightness(brightness);
|
||||
|
||||
// Get the selected color palette
|
||||
CRGBPalette16 myPal = getPalette();
|
||||
|
||||
// Get the current time in milliseconds
|
||||
uint32_t now = millis();
|
||||
|
||||
// Update the animation speed from the UI slider
|
||||
timeScale.setSpeed(speedY);
|
||||
|
||||
// Calculate the current y-offset for animation (makes the fire move)
|
||||
uint32_t y_speed = timeScale.update(now);
|
||||
|
||||
// Loop through every LED in our matrix
|
||||
for (int i = 0; i < HEIGHT; i++) {
|
||||
for (int j = 0; j < WIDTH; j++) {
|
||||
// Calculate which color to use from our palette for this LED
|
||||
uint8_t palette_index = getPaletteIndex(now, i, j, y_speed);
|
||||
|
||||
// Get the actual RGB color from the palette
|
||||
// BRIGHTNESS ensures we use the full brightness range
|
||||
CRGB c = ColorFromPalette(myPal, palette_index, BRIGHTNESS);
|
||||
|
||||
// Convert our 2D coordinates (i,j) to the 1D array index
|
||||
// We use (HEIGHT-1)-i and (WIDTH-1)-j to flip the coordinates
|
||||
// This makes the fire appear to rise from the bottom
|
||||
int index = xyMap((HEIGHT - 1) - i, (WIDTH - 1) - j);
|
||||
|
||||
// Set the LED color in our array
|
||||
leds[index] = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the color data to the actual LEDs
|
||||
FastLED.show();
|
||||
}
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Platform does not have enough memory
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else
|
||||
#include "FireMatrix.h"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@
|
|||
/// 3. Run the FastLED web compiler at root: `fastled`
|
||||
/// 4. When the compiler is done a web page will open.
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
@ -40,6 +40,7 @@ CRGB leds[NUM_LEDS];
|
|||
UITitle title("FxWave2D Demo");
|
||||
UIDescription description("Advanced layered and blended wave effects.");
|
||||
|
||||
UICheckbox xCyclical("X Is Cyclical", false); // If true, waves wrap around the x-axis (like a loop)
|
||||
// Main control UI elements:
|
||||
UIButton button("Trigger"); // Button to trigger a single ripple
|
||||
UIButton buttonFancy("Trigger Fancy"); // Button to trigger a fancy cross-shaped effect
|
||||
|
|
@ -50,7 +51,7 @@ UISlider blurAmount("Global Blur Amount", 0, 0, 172, 1); // Controls overall
|
|||
UISlider blurPasses("Global Blur Passes", 1, 1, 10, 1); // Controls how many times blur is applied (more = smoother but slower)
|
||||
UISlider superSample("SuperSampleExponent", 1.f, 0.f, 3.f, 1.f); // Controls anti-aliasing quality (higher = better quality but more CPU)
|
||||
|
||||
UICheckbox xCyclical("X Is Cyclical", false); // If true, waves wrap around the x-axis (like a loop)
|
||||
|
||||
|
||||
// Upper wave layer controls:
|
||||
UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f); // How fast the upper wave propagates
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
|
|||
83
libraries/FastLED/examples/NoisePlayground/NoisePlayground.h
Normal file
83
libraries/FastLED/examples/NoisePlayground/NoisePlayground.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
|
||||
|
||||
|
||||
/// @file NoisePlayground.ino
|
||||
/// @brief Demonstrates how to use noise generation on a 2D LED matrix
|
||||
/// @example NoisePlayground.ino
|
||||
|
||||
#include <FastLED.h>
|
||||
|
||||
|
||||
// Params for width and height
|
||||
const uint8_t kMatrixWidth = 16;
|
||||
const uint8_t kMatrixHeight = 16;
|
||||
|
||||
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
|
||||
|
||||
// Param for different pixel layouts
|
||||
#define kMatrixSerpentineLayout true
|
||||
|
||||
// led array
|
||||
CRGB leds[kMatrixWidth * kMatrixHeight];
|
||||
|
||||
// x,y, & time values
|
||||
uint32_t x,y,v_time,hue_time,hxy;
|
||||
|
||||
// Play with the values of the variables below and see what kinds of effects they
|
||||
// have! More octaves will make things slower.
|
||||
|
||||
// how many octaves to use for the brightness and hue functions
|
||||
uint8_t octaves=1;
|
||||
uint8_t hue_octaves=3;
|
||||
|
||||
// the 'distance' between points on the x and y axis
|
||||
int xscale=57771;
|
||||
int yscale=57771;
|
||||
|
||||
// the 'distance' between x/y points for the hue noise
|
||||
int hue_scale=1;
|
||||
|
||||
// how fast we move through time & hue noise
|
||||
int time_speed=1111;
|
||||
int hue_speed=31;
|
||||
|
||||
// adjust these values to move along the x or y axis between frames
|
||||
int x_speed=331;
|
||||
int y_speed=1111;
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
// initialize the x/y and time values
|
||||
random16_set_seed(8934);
|
||||
random16_add_entropy(analogRead(3));
|
||||
|
||||
Serial.begin(57600);
|
||||
Serial.println("resetting!");
|
||||
|
||||
delay(3000);
|
||||
FastLED.addLeds<WS2811,2,GRB>(leds,NUM_LEDS);
|
||||
FastLED.setBrightness(96);
|
||||
|
||||
hxy = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
x = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
y = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
v_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
hue_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// fill the led array 2/16-bit noise values
|
||||
fill_2dnoise16(leds, kMatrixWidth, kMatrixHeight, kMatrixSerpentineLayout,
|
||||
octaves,x,xscale,y,yscale,v_time,
|
||||
hue_octaves,hxy,hue_scale,hxy,hue_scale,hue_time, false);
|
||||
|
||||
FastLED.show();
|
||||
|
||||
// adjust the intra-frame time values
|
||||
x += x_speed;
|
||||
y += y_speed;
|
||||
v_time += time_speed;
|
||||
hue_time += hue_speed;
|
||||
// delay(50);
|
||||
}
|
||||
|
|
@ -1,90 +1,11 @@
|
|||
#include <FastLED.h> // Main FastLED library for controlling LEDs
|
||||
|
||||
|
||||
|
||||
/// @file NoisePlayground.ino
|
||||
/// @brief Demonstrates how to use noise generation on a 2D LED matrix
|
||||
/// @example NoisePlayground.ino
|
||||
|
||||
#include <FastLED.h>
|
||||
|
||||
#if defined(__AVR__)
|
||||
// too large for ATtiny85, attiny88, etc.. Just disable it for all avr boards.
|
||||
void setup() {};
|
||||
void loop() {};
|
||||
#else
|
||||
|
||||
// Params for width and height
|
||||
const uint8_t kMatrixWidth = 16;
|
||||
const uint8_t kMatrixHeight = 16;
|
||||
|
||||
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
|
||||
|
||||
// Param for different pixel layouts
|
||||
#define kMatrixSerpentineLayout true
|
||||
|
||||
// led array
|
||||
CRGB leds[kMatrixWidth * kMatrixHeight];
|
||||
|
||||
// x,y, & time values
|
||||
uint32_t x,y,v_time,hue_time,hxy;
|
||||
|
||||
// Play with the values of the variables below and see what kinds of effects they
|
||||
// have! More octaves will make things slower.
|
||||
|
||||
// how many octaves to use for the brightness and hue functions
|
||||
uint8_t octaves=1;
|
||||
uint8_t hue_octaves=3;
|
||||
|
||||
// the 'distance' between points on the x and y axis
|
||||
int xscale=57771;
|
||||
int yscale=57771;
|
||||
|
||||
// the 'distance' between x/y points for the hue noise
|
||||
int hue_scale=1;
|
||||
|
||||
// how fast we move through time & hue noise
|
||||
int time_speed=1111;
|
||||
int hue_speed=31;
|
||||
|
||||
// adjust these values to move along the x or y axis between frames
|
||||
int x_speed=331;
|
||||
int y_speed=1111;
|
||||
|
||||
void loop() {
|
||||
// fill the led array 2/16-bit noise values
|
||||
fill_2dnoise16(leds, kMatrixWidth, kMatrixHeight, kMatrixSerpentineLayout,
|
||||
octaves,x,xscale,y,yscale,v_time,
|
||||
hue_octaves,hxy,hue_scale,hxy,hue_scale,hue_time, false);
|
||||
|
||||
FastLED.show();
|
||||
|
||||
// adjust the intra-frame time values
|
||||
x += x_speed;
|
||||
y += y_speed;
|
||||
v_time += time_speed;
|
||||
hue_time += hue_speed;
|
||||
// delay(50);
|
||||
}
|
||||
|
||||
|
||||
void setup() {
|
||||
// initialize the x/y and time values
|
||||
random16_set_seed(8934);
|
||||
random16_add_entropy(analogRead(3));
|
||||
|
||||
Serial.begin(57600);
|
||||
Serial.println("resetting!");
|
||||
|
||||
delay(3000);
|
||||
FastLED.addLeds<WS2811,2,GRB>(leds,NUM_LEDS);
|
||||
FastLED.setBrightness(96);
|
||||
|
||||
hxy = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
x = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
y = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
v_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
hue_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically
|
||||
// don't have enough memory to handle this complex animation.
|
||||
// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing.
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else // For all other platforms with more memory (ESP32, Teensy, etc.)
|
||||
#include "NoisePlayground.h"
|
||||
#endif // End of the non-AVR code section
|
||||
|
|
|
|||
407
libraries/FastLED/examples/NoisePlusPalette/NoisePlusPalette.h
Normal file
407
libraries/FastLED/examples/NoisePlusPalette/NoisePlusPalette.h
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
/// @file NoisePlusPalette.ino
|
||||
/// @brief Demonstrates how to mix noise generation with color palettes on a 2D LED matrix
|
||||
/// @example NoisePlusPalette.ino
|
||||
///
|
||||
/// OVERVIEW:
|
||||
/// This sketch demonstrates combining Perlin noise with color palettes to create
|
||||
/// dynamic, flowing color patterns on an LED matrix. The noise function creates
|
||||
/// natural-looking patterns that change over time, while the color palettes
|
||||
/// determine which colors are used to visualize the noise values.
|
||||
|
||||
#include <FastLED.h> // Main FastLED library for controlling LEDs
|
||||
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically
|
||||
// don't have enough memory to handle this complex animation.
|
||||
// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing.
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else // For all other platforms with more memory (ESP32, Teensy, etc.)
|
||||
|
||||
// LED hardware configuration
|
||||
#define LED_PIN 3 // Data pin connected to the LED strip
|
||||
#define BRIGHTNESS 96 // Default brightness level (0-255)
|
||||
#define LED_TYPE WS2811 // Type of LED strip being used
|
||||
#define COLOR_ORDER GRB // Color order of the LEDs (varies by strip type)
|
||||
|
||||
// Matrix dimensions - defines the size of our virtual LED grid
|
||||
const uint8_t kMatrixWidth = 16; // Number of columns in the matrix
|
||||
const uint8_t kMatrixHeight = 16; // Number of rows in the matrix
|
||||
|
||||
// LED strip layout configuration
|
||||
const bool kMatrixSerpentineLayout = true; // If true, every other row runs backwards
|
||||
// This is common in matrix setups to allow
|
||||
// for easier wiring
|
||||
|
||||
|
||||
// HOW THIS EXAMPLE WORKS:
|
||||
//
|
||||
// This example combines two features of FastLED to produce a remarkable range of
|
||||
// effects from a relatively small amount of code. This example combines FastLED's
|
||||
// color palette lookup functions with FastLED's Perlin noise generator, and
|
||||
// the combination is extremely powerful.
|
||||
//
|
||||
// You might want to look at the "ColorPalette" and "Noise" examples separately
|
||||
// if this example code seems daunting.
|
||||
//
|
||||
//
|
||||
// The basic setup here is that for each frame, we generate a new array of
|
||||
// 'noise' data, and then map it onto the LED matrix through a color palette.
|
||||
//
|
||||
// Periodically, the color palette is changed, and new noise-generation parameters
|
||||
// are chosen at the same time. In this example, specific noise-generation
|
||||
// values have been selected to match the given color palettes; some are faster,
|
||||
// or slower, or larger, or smaller than others, but there's no reason these
|
||||
// parameters can't be freely mixed-and-matched.
|
||||
//
|
||||
// In addition, this example includes some fast automatic 'data smoothing' at
|
||||
// lower noise speeds to help produce smoother animations in those cases.
|
||||
//
|
||||
// The FastLED built-in color palettes (Forest, Clouds, Lava, Ocean, Party) are
|
||||
// used, as well as some 'hand-defined' ones, and some procedurally generated
|
||||
// palettes.
|
||||
|
||||
|
||||
// Calculate the total number of LEDs in our matrix
|
||||
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
|
||||
|
||||
// Find the larger dimension (width or height) for our noise array size
|
||||
#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight)
|
||||
|
||||
// Array to hold all LED color values - one CRGB struct per LED
|
||||
CRGB leds[kMatrixWidth * kMatrixHeight];
|
||||
|
||||
// The 16-bit version of our coordinates for the noise function
|
||||
// Using 16 bits gives us more resolution and smoother animations
|
||||
static uint16_t x; // x-coordinate in the noise space
|
||||
static uint16_t y; // y-coordinate in the noise space
|
||||
static uint16_t z; // z-coordinate (time dimension) in the noise space
|
||||
|
||||
// ANIMATION PARAMETERS:
|
||||
|
||||
// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll
|
||||
// use the z-axis for "time". speed determines how fast time moves forward. Try
|
||||
// 1 for a very slow moving effect, or 60 for something that ends up looking like
|
||||
// water.
|
||||
uint16_t speed = 20; // Speed is set dynamically once we've started up
|
||||
// Higher values = faster animation
|
||||
|
||||
// Scale determines how far apart the pixels in our noise matrix are. Try
|
||||
// changing these values around to see how it affects the motion of the display. The
|
||||
// higher the value of scale, the more "zoomed out" the noise will be. A value
|
||||
// of 1 will be so zoomed in, you'll mostly see solid colors.
|
||||
uint16_t scale = 30; // Scale is set dynamically once we've started up
|
||||
// Higher values = more "zoomed out" pattern
|
||||
|
||||
// This is the array that we keep our computed noise values in
|
||||
// Each position stores an 8-bit (0-255) noise value
|
||||
uint8_t noise[MAX_DIMENSION][MAX_DIMENSION];
|
||||
|
||||
// The current color palette we're using to map noise values to colors
|
||||
CRGBPalette16 currentPalette( PartyColors_p ); // Start with party colors
|
||||
|
||||
// If colorLoop is set to 1, we'll cycle through the colors in the palette
|
||||
// This creates an additional animation effect on top of the noise movement
|
||||
uint8_t colorLoop = 1; // 0 = no color cycling, 1 = cycle colors
|
||||
|
||||
|
||||
// Forward declare our functions so that we have maximum compatibility
|
||||
// with other build tools outside of ArduinoIDE. The *.ino files are
|
||||
// special in that Arduino will generate function prototypes for you.
|
||||
// For non-Arduino environments, we need these declarations.
|
||||
void SetupRandomPalette(); // Creates a random color palette
|
||||
void SetupPurpleAndGreenPalette(); // Creates a purple and green striped palette
|
||||
void SetupBlackAndWhiteStripedPalette(); // Creates a black and white striped palette
|
||||
void ChangePaletteAndSettingsPeriodically(); // Changes palettes and settings over time
|
||||
void mapNoiseToLEDsUsingPalette(); // Maps noise data to LED colors using the palette
|
||||
uint16_t XY( uint8_t x, uint8_t y); // Converts x,y coordinates to LED array index
|
||||
|
||||
void setup() {
|
||||
delay(3000); // 3 second delay for recovery and to give time for the serial monitor to open
|
||||
|
||||
// Initialize the LED strip:
|
||||
// - LED_TYPE specifies the chipset (WS2811, WS2812B, etc.)
|
||||
// - LED_PIN is the data pin number
|
||||
// - COLOR_ORDER specifies the RGB color ordering for your strip
|
||||
FastLED.addLeds<LED_TYPE,LED_PIN,COLOR_ORDER>(leds,NUM_LEDS);
|
||||
// NOTE - This does NOT have a ScreenMap (because it's a legacy sketch)
|
||||
// so it won't look that good on the web-compiler. But adding it is ONE LINE!
|
||||
|
||||
// Set the overall brightness level (0-255)
|
||||
FastLED.setBrightness(BRIGHTNESS);
|
||||
|
||||
// Initialize our noise coordinates to random values
|
||||
// This ensures the pattern starts from a different position each time
|
||||
x = random16(); // Random x starting position
|
||||
y = random16(); // Random y starting position
|
||||
z = random16(); // Random time starting position
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Fill the x/y array of 8-bit noise values using the inoise8 function.
|
||||
void fillnoise8() {
|
||||
// If we're running at a low "speed", some 8-bit artifacts become visible
|
||||
// from frame-to-frame. In order to reduce this, we can do some fast data-smoothing.
|
||||
// The amount of data smoothing we're doing depends on "speed".
|
||||
uint8_t dataSmoothing = 0;
|
||||
if( speed < 50) {
|
||||
// At lower speeds, apply more smoothing
|
||||
// This formula creates more smoothing at lower speeds:
|
||||
// speed=10 → smoothing=160, speed=30 → smoothing=80
|
||||
dataSmoothing = 200 - (speed * 4);
|
||||
}
|
||||
|
||||
// Loop through each pixel in our noise array
|
||||
for(int i = 0; i < MAX_DIMENSION; i++) {
|
||||
// Calculate the offset for this pixel in the x dimension
|
||||
int ioffset = scale * i;
|
||||
|
||||
for(int j = 0; j < MAX_DIMENSION; j++) {
|
||||
// Calculate the offset for this pixel in the y dimension
|
||||
int joffset = scale * j;
|
||||
|
||||
// Generate the noise value for this pixel using 3D Perlin noise
|
||||
// The noise function takes x, y, and z (time) coordinates
|
||||
uint8_t data = inoise8(x + ioffset, y + joffset, z);
|
||||
|
||||
// The range of the inoise8 function is roughly 16-238.
|
||||
// These two operations expand those values out to roughly 0..255
|
||||
// You can comment them out if you want the raw noise data.
|
||||
data = qsub8(data, 16); // Subtract 16 (with underflow protection)
|
||||
data = qadd8(data, scale8(data, 39)); // Add a scaled version of the data to itself
|
||||
|
||||
// Apply data smoothing if enabled
|
||||
if( dataSmoothing ) {
|
||||
uint8_t olddata = noise[i][j]; // Get the previous frame's value
|
||||
|
||||
// Blend between old and new data based on smoothing amount
|
||||
// Higher dataSmoothing = more of the old value is kept
|
||||
uint8_t newdata = scale8(olddata, dataSmoothing) +
|
||||
scale8(data, 256 - dataSmoothing);
|
||||
data = newdata;
|
||||
}
|
||||
|
||||
// Store the final noise value in our array
|
||||
noise[i][j] = data;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment z to move through the noise space over time
|
||||
z += speed;
|
||||
|
||||
// Apply slow drift to X and Y, just for visual variation
|
||||
// This creates a gentle shifting of the entire pattern
|
||||
x += speed / 8; // X drifts at 1/8 the speed of z
|
||||
y -= speed / 16; // Y drifts at 1/16 the speed of z in the opposite direction
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Map the noise data to LED colors using the current color palette
|
||||
void mapNoiseToLEDsUsingPalette()
|
||||
{
|
||||
// Static variable that slowly increases to cycle through colors when colorLoop is enabled
|
||||
static uint8_t ihue=0;
|
||||
|
||||
// Loop through each pixel in our LED matrix
|
||||
for(int i = 0; i < kMatrixWidth; i++) {
|
||||
for(int j = 0; j < kMatrixHeight; j++) {
|
||||
// We use the value at the (i,j) coordinate in the noise
|
||||
// array for our brightness, and the flipped value from (j,i)
|
||||
// for our pixel's index into the color palette.
|
||||
// This creates interesting patterns with two different noise mappings.
|
||||
|
||||
uint8_t index = noise[j][i]; // Color index from the flipped coordinate
|
||||
uint8_t bri = noise[i][j]; // Brightness from the normal coordinate
|
||||
|
||||
// If color cycling is enabled, add a slowly-changing base value to the index
|
||||
// This makes the colors shift/rotate through the palette over time
|
||||
if( colorLoop) {
|
||||
index += ihue; // Add the slowly increasing hue offset
|
||||
}
|
||||
|
||||
// Brighten up the colors, as the color palette itself often contains the
|
||||
// light/dark dynamic range desired
|
||||
if( bri > 127 ) {
|
||||
// If brightness is in the upper half, make it full brightness
|
||||
bri = 255;
|
||||
} else {
|
||||
// Otherwise, scale it to the full range (0-127 becomes 0-254)
|
||||
bri = dim8_raw( bri * 2);
|
||||
}
|
||||
|
||||
// Get the final color by looking up the palette color at our index
|
||||
// and applying the brightness value
|
||||
CRGB color = ColorFromPalette( currentPalette, index, bri);
|
||||
|
||||
// Set the LED color in our array, using the XY mapping function
|
||||
// to convert from x,y coordinates to the 1D array index
|
||||
leds[XY(i,j)] = color;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment the hue value for the next frame (for color cycling)
|
||||
ihue+=1;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// The main program loop that runs continuously
|
||||
|
||||
// Periodically choose a new palette, speed, and scale
|
||||
// This creates variety in the animation over time
|
||||
ChangePaletteAndSettingsPeriodically();
|
||||
|
||||
// Generate new noise data for this frame
|
||||
fillnoise8();
|
||||
|
||||
// Convert the noise data to colors in the LED array
|
||||
// using the current palette
|
||||
mapNoiseToLEDsUsingPalette();
|
||||
|
||||
// Send the color data to the actual LEDs
|
||||
FastLED.show();
|
||||
|
||||
// No delay is needed here as the calculations already take some time
|
||||
// Adding a delay would slow down the animation
|
||||
// delay(10);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// PALETTE MANAGEMENT:
|
||||
//
|
||||
// There are several different palettes of colors demonstrated here.
|
||||
//
|
||||
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
|
||||
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
|
||||
//
|
||||
// Additionally, you can manually define your own color palettes, or you can write
|
||||
// code that creates color palettes on the fly.
|
||||
|
||||
// This controls how long each palette is displayed before changing
|
||||
// 1 = 5 sec per palette
|
||||
// 2 = 10 sec per palette
|
||||
// etc.
|
||||
#define HOLD_PALETTES_X_TIMES_AS_LONG 1 // Multiplier for palette duration
|
||||
|
||||
// Periodically change the palette, speed, and scale settings
|
||||
void ChangePaletteAndSettingsPeriodically()
|
||||
{
|
||||
// Calculate which "second hand" we're on (0-59) based on elapsed time
|
||||
// We divide by HOLD_PALETTES_X_TIMES_AS_LONG to slow down the changes
|
||||
uint8_t secondHand = ((millis() / 1000) / HOLD_PALETTES_X_TIMES_AS_LONG) % 60;
|
||||
static uint8_t lastSecond = 99; // Track the last second to detect changes
|
||||
|
||||
// Only update when the second hand changes
|
||||
if( lastSecond != secondHand) {
|
||||
lastSecond = secondHand;
|
||||
|
||||
// Every 5 seconds, change to a different palette and settings
|
||||
// Each palette has specific speed and scale settings that work well with it
|
||||
|
||||
if( secondHand == 0) { currentPalette = RainbowColors_p; speed = 20; scale = 30; colorLoop = 1; }
|
||||
if( secondHand == 5) { SetupPurpleAndGreenPalette(); speed = 10; scale = 50; colorLoop = 1; }
|
||||
if( secondHand == 10) { SetupBlackAndWhiteStripedPalette(); speed = 20; scale = 30; colorLoop = 1; }
|
||||
if( secondHand == 15) { currentPalette = ForestColors_p; speed = 8; scale =120; colorLoop = 0; }
|
||||
if( secondHand == 20) { currentPalette = CloudColors_p; speed = 4; scale = 30; colorLoop = 0; }
|
||||
if( secondHand == 25) { currentPalette = LavaColors_p; speed = 8; scale = 50; colorLoop = 0; }
|
||||
if( secondHand == 30) { currentPalette = OceanColors_p; speed = 20; scale = 90; colorLoop = 0; }
|
||||
if( secondHand == 35) { currentPalette = PartyColors_p; speed = 20; scale = 30; colorLoop = 1; }
|
||||
if( secondHand == 40) { SetupRandomPalette(); speed = 20; scale = 20; colorLoop = 1; }
|
||||
if( secondHand == 45) { SetupRandomPalette(); speed = 50; scale = 50; colorLoop = 1; }
|
||||
if( secondHand == 50) { SetupRandomPalette(); speed = 90; scale = 90; colorLoop = 1; }
|
||||
if( secondHand == 55) { currentPalette = RainbowStripeColors_p; speed = 30; scale = 20; colorLoop = 1; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This function generates a random palette that's a gradient
|
||||
// between four different colors. The first is a dim hue, the second is
|
||||
// a bright hue, the third is a bright pastel, and the last is
|
||||
// another bright hue. This gives some visual bright/dark variation
|
||||
// which is more interesting than just a gradient of different hues.
|
||||
void SetupRandomPalette()
|
||||
{
|
||||
// Create a new palette with 4 random colors that blend together
|
||||
currentPalette = CRGBPalette16(
|
||||
CHSV( random8(), 255, 32), // Random dim hue (low value)
|
||||
CHSV( random8(), 255, 255), // Random bright hue (full saturation & value)
|
||||
CHSV( random8(), 128, 255), // Random pastel (medium saturation, full value)
|
||||
CHSV( random8(), 255, 255)); // Another random bright hue
|
||||
|
||||
// The CRGBPalette16 constructor automatically creates a 16-color gradient
|
||||
// between these four colors, evenly distributed
|
||||
}
|
||||
|
||||
|
||||
// This function sets up a palette of black and white stripes,
|
||||
// using code. Since the palette is effectively an array of
|
||||
// sixteen CRGB colors, the various fill_* functions can be used
|
||||
// to set them up.
|
||||
void SetupBlackAndWhiteStripedPalette()
|
||||
{
|
||||
// 'black out' all 16 palette entries...
|
||||
fill_solid( currentPalette, 16, CRGB::Black);
|
||||
|
||||
// and set every fourth one to white to create stripes
|
||||
// Positions 0, 4, 8, and 12 in the 16-color palette
|
||||
currentPalette[0] = CRGB::White;
|
||||
currentPalette[4] = CRGB::White;
|
||||
currentPalette[8] = CRGB::White;
|
||||
currentPalette[12] = CRGB::White;
|
||||
|
||||
// The palette interpolation will create smooth transitions between these colors
|
||||
}
|
||||
|
||||
// This function sets up a palette of purple and green stripes.
|
||||
void SetupPurpleAndGreenPalette()
|
||||
{
|
||||
// Define our colors using HSV color space for consistency
|
||||
CRGB purple = CHSV( HUE_PURPLE, 255, 255); // Bright purple
|
||||
CRGB green = CHSV( HUE_GREEN, 255, 255); // Bright green
|
||||
CRGB black = CRGB::Black; // Black
|
||||
|
||||
// Create a 16-color palette with a specific pattern:
|
||||
// green-green-black-black-purple-purple-black-black, repeated twice
|
||||
// This creates alternating green and purple stripes with black in between
|
||||
currentPalette = CRGBPalette16(
|
||||
green, green, black, black, // First 4 colors
|
||||
purple, purple, black, black, // Next 4 colors
|
||||
green, green, black, black, // Repeat the pattern
|
||||
purple, purple, black, black ); // Last 4 colors
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Mark's xy coordinate mapping code. See the XYMatrix for more information on it.
|
||||
//
|
||||
// This function converts x,y coordinates to a single array index
|
||||
// It handles both regular and serpentine matrix layouts
|
||||
uint16_t XY( uint8_t x, uint8_t y)
|
||||
{
|
||||
uint16_t i;
|
||||
|
||||
// For a regular/sequential layout, it's just y * width + x
|
||||
if( kMatrixSerpentineLayout == false) {
|
||||
i = (y * kMatrixWidth) + x;
|
||||
}
|
||||
|
||||
// For a serpentine layout (zigzag), odd rows run backwards
|
||||
if( kMatrixSerpentineLayout == true) {
|
||||
if( y & 0x01) { // Check if y is odd (bitwise AND with 1)
|
||||
// Odd rows run backwards
|
||||
uint8_t reverseX = (kMatrixWidth - 1) - x;
|
||||
i = (y * kMatrixWidth) + reverseX;
|
||||
} else {
|
||||
// Even rows run forwards
|
||||
i = (y * kMatrixWidth) + x;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
#endif // End of the non-AVR code section
|
||||
|
|
@ -1,407 +1,11 @@
|
|||
/// @file NoisePlusPalette.ino
|
||||
/// @brief Demonstrates how to mix noise generation with color palettes on a 2D LED matrix
|
||||
/// @example NoisePlusPalette.ino
|
||||
///
|
||||
/// OVERVIEW:
|
||||
/// This sketch demonstrates combining Perlin noise with color palettes to create
|
||||
/// dynamic, flowing color patterns on an LED matrix. The noise function creates
|
||||
/// natural-looking patterns that change over time, while the color palettes
|
||||
/// determine which colors are used to visualize the noise values.
|
||||
|
||||
#include <FastLED.h> // Main FastLED library for controlling LEDs
|
||||
|
||||
#ifdef __AVR__
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Don't compile this for AVR microcontrollers (like Arduino Uno) because they typically
|
||||
// don't have enough memory to handle this complex animation.
|
||||
// Instead, we provide empty setup/loop functions so the sketch will compile but do nothing.
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
#else // For all other platforms with more memory (ESP32, Teensy, etc.)
|
||||
|
||||
// LED hardware configuration
|
||||
#define LED_PIN 3 // Data pin connected to the LED strip
|
||||
#define BRIGHTNESS 96 // Default brightness level (0-255)
|
||||
#define LED_TYPE WS2811 // Type of LED strip being used
|
||||
#define COLOR_ORDER GRB // Color order of the LEDs (varies by strip type)
|
||||
|
||||
// Matrix dimensions - defines the size of our virtual LED grid
|
||||
const uint8_t kMatrixWidth = 16; // Number of columns in the matrix
|
||||
const uint8_t kMatrixHeight = 16; // Number of rows in the matrix
|
||||
|
||||
// LED strip layout configuration
|
||||
const bool kMatrixSerpentineLayout = true; // If true, every other row runs backwards
|
||||
// This is common in matrix setups to allow
|
||||
// for easier wiring
|
||||
|
||||
|
||||
// HOW THIS EXAMPLE WORKS:
|
||||
//
|
||||
// This example combines two features of FastLED to produce a remarkable range of
|
||||
// effects from a relatively small amount of code. This example combines FastLED's
|
||||
// color palette lookup functions with FastLED's Perlin noise generator, and
|
||||
// the combination is extremely powerful.
|
||||
//
|
||||
// You might want to look at the "ColorPalette" and "Noise" examples separately
|
||||
// if this example code seems daunting.
|
||||
//
|
||||
//
|
||||
// The basic setup here is that for each frame, we generate a new array of
|
||||
// 'noise' data, and then map it onto the LED matrix through a color palette.
|
||||
//
|
||||
// Periodically, the color palette is changed, and new noise-generation parameters
|
||||
// are chosen at the same time. In this example, specific noise-generation
|
||||
// values have been selected to match the given color palettes; some are faster,
|
||||
// or slower, or larger, or smaller than others, but there's no reason these
|
||||
// parameters can't be freely mixed-and-matched.
|
||||
//
|
||||
// In addition, this example includes some fast automatic 'data smoothing' at
|
||||
// lower noise speeds to help produce smoother animations in those cases.
|
||||
//
|
||||
// The FastLED built-in color palettes (Forest, Clouds, Lava, Ocean, Party) are
|
||||
// used, as well as some 'hand-defined' ones, and some procedurally generated
|
||||
// palettes.
|
||||
|
||||
|
||||
// Calculate the total number of LEDs in our matrix
|
||||
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
|
||||
|
||||
// Find the larger dimension (width or height) for our noise array size
|
||||
#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight)
|
||||
|
||||
// Array to hold all LED color values - one CRGB struct per LED
|
||||
CRGB leds[kMatrixWidth * kMatrixHeight];
|
||||
|
||||
// The 16-bit version of our coordinates for the noise function
|
||||
// Using 16 bits gives us more resolution and smoother animations
|
||||
static uint16_t x; // x-coordinate in the noise space
|
||||
static uint16_t y; // y-coordinate in the noise space
|
||||
static uint16_t z; // z-coordinate (time dimension) in the noise space
|
||||
|
||||
// ANIMATION PARAMETERS:
|
||||
|
||||
// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll
|
||||
// use the z-axis for "time". speed determines how fast time moves forward. Try
|
||||
// 1 for a very slow moving effect, or 60 for something that ends up looking like
|
||||
// water.
|
||||
uint16_t speed = 20; // Speed is set dynamically once we've started up
|
||||
// Higher values = faster animation
|
||||
|
||||
// Scale determines how far apart the pixels in our noise matrix are. Try
|
||||
// changing these values around to see how it affects the motion of the display. The
|
||||
// higher the value of scale, the more "zoomed out" the noise will be. A value
|
||||
// of 1 will be so zoomed in, you'll mostly see solid colors.
|
||||
uint16_t scale = 30; // Scale is set dynamically once we've started up
|
||||
// Higher values = more "zoomed out" pattern
|
||||
|
||||
// This is the array that we keep our computed noise values in
|
||||
// Each position stores an 8-bit (0-255) noise value
|
||||
uint8_t noise[MAX_DIMENSION][MAX_DIMENSION];
|
||||
|
||||
// The current color palette we're using to map noise values to colors
|
||||
CRGBPalette16 currentPalette( PartyColors_p ); // Start with party colors
|
||||
|
||||
// If colorLoop is set to 1, we'll cycle through the colors in the palette
|
||||
// This creates an additional animation effect on top of the noise movement
|
||||
uint8_t colorLoop = 1; // 0 = no color cycling, 1 = cycle colors
|
||||
|
||||
|
||||
// Forward declare our functions so that we have maximum compatibility
|
||||
// with other build tools outside of ArduinoIDE. The *.ino files are
|
||||
// special in that Arduino will generate function prototypes for you.
|
||||
// For non-Arduino environments, we need these declarations.
|
||||
void SetupRandomPalette(); // Creates a random color palette
|
||||
void SetupPurpleAndGreenPalette(); // Creates a purple and green striped palette
|
||||
void SetupBlackAndWhiteStripedPalette(); // Creates a black and white striped palette
|
||||
void ChangePaletteAndSettingsPeriodically(); // Changes palettes and settings over time
|
||||
void mapNoiseToLEDsUsingPalette(); // Maps noise data to LED colors using the palette
|
||||
uint16_t XY( uint8_t x, uint8_t y); // Converts x,y coordinates to LED array index
|
||||
|
||||
void setup() {
|
||||
delay(3000); // 3 second delay for recovery and to give time for the serial monitor to open
|
||||
|
||||
// Initialize the LED strip:
|
||||
// - LED_TYPE specifies the chipset (WS2811, WS2812B, etc.)
|
||||
// - LED_PIN is the data pin number
|
||||
// - COLOR_ORDER specifies the RGB color ordering for your strip
|
||||
FastLED.addLeds<LED_TYPE,LED_PIN,COLOR_ORDER>(leds,NUM_LEDS);
|
||||
// NOTE - This does NOT have a ScreenMap (because it's a legacy sketch)
|
||||
// so it won't look that good on the web-compiler. But adding it is ONE LINE!
|
||||
|
||||
// Set the overall brightness level (0-255)
|
||||
FastLED.setBrightness(BRIGHTNESS);
|
||||
|
||||
// Initialize our noise coordinates to random values
|
||||
// This ensures the pattern starts from a different position each time
|
||||
x = random16(); // Random x starting position
|
||||
y = random16(); // Random y starting position
|
||||
z = random16(); // Random time starting position
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Fill the x/y array of 8-bit noise values using the inoise8 function.
|
||||
void fillnoise8() {
|
||||
// If we're running at a low "speed", some 8-bit artifacts become visible
|
||||
// from frame-to-frame. In order to reduce this, we can do some fast data-smoothing.
|
||||
// The amount of data smoothing we're doing depends on "speed".
|
||||
uint8_t dataSmoothing = 0;
|
||||
if( speed < 50) {
|
||||
// At lower speeds, apply more smoothing
|
||||
// This formula creates more smoothing at lower speeds:
|
||||
// speed=10 → smoothing=160, speed=30 → smoothing=80
|
||||
dataSmoothing = 200 - (speed * 4);
|
||||
}
|
||||
|
||||
// Loop through each pixel in our noise array
|
||||
for(int i = 0; i < MAX_DIMENSION; i++) {
|
||||
// Calculate the offset for this pixel in the x dimension
|
||||
int ioffset = scale * i;
|
||||
|
||||
for(int j = 0; j < MAX_DIMENSION; j++) {
|
||||
// Calculate the offset for this pixel in the y dimension
|
||||
int joffset = scale * j;
|
||||
|
||||
// Generate the noise value for this pixel using 3D Perlin noise
|
||||
// The noise function takes x, y, and z (time) coordinates
|
||||
uint8_t data = inoise8(x + ioffset, y + joffset, z);
|
||||
|
||||
// The range of the inoise8 function is roughly 16-238.
|
||||
// These two operations expand those values out to roughly 0..255
|
||||
// You can comment them out if you want the raw noise data.
|
||||
data = qsub8(data, 16); // Subtract 16 (with underflow protection)
|
||||
data = qadd8(data, scale8(data, 39)); // Add a scaled version of the data to itself
|
||||
|
||||
// Apply data smoothing if enabled
|
||||
if( dataSmoothing ) {
|
||||
uint8_t olddata = noise[i][j]; // Get the previous frame's value
|
||||
|
||||
// Blend between old and new data based on smoothing amount
|
||||
// Higher dataSmoothing = more of the old value is kept
|
||||
uint8_t newdata = scale8(olddata, dataSmoothing) +
|
||||
scale8(data, 256 - dataSmoothing);
|
||||
data = newdata;
|
||||
}
|
||||
|
||||
// Store the final noise value in our array
|
||||
noise[i][j] = data;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment z to move through the noise space over time
|
||||
z += speed;
|
||||
|
||||
// Apply slow drift to X and Y, just for visual variation
|
||||
// This creates a gentle shifting of the entire pattern
|
||||
x += speed / 8; // X drifts at 1/8 the speed of z
|
||||
y -= speed / 16; // Y drifts at 1/16 the speed of z in the opposite direction
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Map the noise data to LED colors using the current color palette
|
||||
void mapNoiseToLEDsUsingPalette()
|
||||
{
|
||||
// Static variable that slowly increases to cycle through colors when colorLoop is enabled
|
||||
static uint8_t ihue=0;
|
||||
|
||||
// Loop through each pixel in our LED matrix
|
||||
for(int i = 0; i < kMatrixWidth; i++) {
|
||||
for(int j = 0; j < kMatrixHeight; j++) {
|
||||
// We use the value at the (i,j) coordinate in the noise
|
||||
// array for our brightness, and the flipped value from (j,i)
|
||||
// for our pixel's index into the color palette.
|
||||
// This creates interesting patterns with two different noise mappings.
|
||||
|
||||
uint8_t index = noise[j][i]; // Color index from the flipped coordinate
|
||||
uint8_t bri = noise[i][j]; // Brightness from the normal coordinate
|
||||
|
||||
// If color cycling is enabled, add a slowly-changing base value to the index
|
||||
// This makes the colors shift/rotate through the palette over time
|
||||
if( colorLoop) {
|
||||
index += ihue; // Add the slowly increasing hue offset
|
||||
}
|
||||
|
||||
// Brighten up the colors, as the color palette itself often contains the
|
||||
// light/dark dynamic range desired
|
||||
if( bri > 127 ) {
|
||||
// If brightness is in the upper half, make it full brightness
|
||||
bri = 255;
|
||||
} else {
|
||||
// Otherwise, scale it to the full range (0-127 becomes 0-254)
|
||||
bri = dim8_raw( bri * 2);
|
||||
}
|
||||
|
||||
// Get the final color by looking up the palette color at our index
|
||||
// and applying the brightness value
|
||||
CRGB color = ColorFromPalette( currentPalette, index, bri);
|
||||
|
||||
// Set the LED color in our array, using the XY mapping function
|
||||
// to convert from x,y coordinates to the 1D array index
|
||||
leds[XY(i,j)] = color;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment the hue value for the next frame (for color cycling)
|
||||
ihue+=1;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// The main program loop that runs continuously
|
||||
|
||||
// Periodically choose a new palette, speed, and scale
|
||||
// This creates variety in the animation over time
|
||||
ChangePaletteAndSettingsPeriodically();
|
||||
|
||||
// Generate new noise data for this frame
|
||||
fillnoise8();
|
||||
|
||||
// Convert the noise data to colors in the LED array
|
||||
// using the current palette
|
||||
mapNoiseToLEDsUsingPalette();
|
||||
|
||||
// Send the color data to the actual LEDs
|
||||
FastLED.show();
|
||||
|
||||
// No delay is needed here as the calculations already take some time
|
||||
// Adding a delay would slow down the animation
|
||||
// delay(10);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// PALETTE MANAGEMENT:
|
||||
//
|
||||
// There are several different palettes of colors demonstrated here.
|
||||
//
|
||||
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
|
||||
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
|
||||
//
|
||||
// Additionally, you can manually define your own color palettes, or you can write
|
||||
// code that creates color palettes on the fly.
|
||||
|
||||
// This controls how long each palette is displayed before changing
|
||||
// 1 = 5 sec per palette
|
||||
// 2 = 10 sec per palette
|
||||
// etc.
|
||||
#define HOLD_PALETTES_X_TIMES_AS_LONG 1 // Multiplier for palette duration
|
||||
|
||||
// Periodically change the palette, speed, and scale settings
|
||||
void ChangePaletteAndSettingsPeriodically()
|
||||
{
|
||||
// Calculate which "second hand" we're on (0-59) based on elapsed time
|
||||
// We divide by HOLD_PALETTES_X_TIMES_AS_LONG to slow down the changes
|
||||
uint8_t secondHand = ((millis() / 1000) / HOLD_PALETTES_X_TIMES_AS_LONG) % 60;
|
||||
static uint8_t lastSecond = 99; // Track the last second to detect changes
|
||||
|
||||
// Only update when the second hand changes
|
||||
if( lastSecond != secondHand) {
|
||||
lastSecond = secondHand;
|
||||
|
||||
// Every 5 seconds, change to a different palette and settings
|
||||
// Each palette has specific speed and scale settings that work well with it
|
||||
|
||||
if( secondHand == 0) { currentPalette = RainbowColors_p; speed = 20; scale = 30; colorLoop = 1; }
|
||||
if( secondHand == 5) { SetupPurpleAndGreenPalette(); speed = 10; scale = 50; colorLoop = 1; }
|
||||
if( secondHand == 10) { SetupBlackAndWhiteStripedPalette(); speed = 20; scale = 30; colorLoop = 1; }
|
||||
if( secondHand == 15) { currentPalette = ForestColors_p; speed = 8; scale =120; colorLoop = 0; }
|
||||
if( secondHand == 20) { currentPalette = CloudColors_p; speed = 4; scale = 30; colorLoop = 0; }
|
||||
if( secondHand == 25) { currentPalette = LavaColors_p; speed = 8; scale = 50; colorLoop = 0; }
|
||||
if( secondHand == 30) { currentPalette = OceanColors_p; speed = 20; scale = 90; colorLoop = 0; }
|
||||
if( secondHand == 35) { currentPalette = PartyColors_p; speed = 20; scale = 30; colorLoop = 1; }
|
||||
if( secondHand == 40) { SetupRandomPalette(); speed = 20; scale = 20; colorLoop = 1; }
|
||||
if( secondHand == 45) { SetupRandomPalette(); speed = 50; scale = 50; colorLoop = 1; }
|
||||
if( secondHand == 50) { SetupRandomPalette(); speed = 90; scale = 90; colorLoop = 1; }
|
||||
if( secondHand == 55) { currentPalette = RainbowStripeColors_p; speed = 30; scale = 20; colorLoop = 1; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This function generates a random palette that's a gradient
|
||||
// between four different colors. The first is a dim hue, the second is
|
||||
// a bright hue, the third is a bright pastel, and the last is
|
||||
// another bright hue. This gives some visual bright/dark variation
|
||||
// which is more interesting than just a gradient of different hues.
|
||||
void SetupRandomPalette()
|
||||
{
|
||||
// Create a new palette with 4 random colors that blend together
|
||||
currentPalette = CRGBPalette16(
|
||||
CHSV( random8(), 255, 32), // Random dim hue (low value)
|
||||
CHSV( random8(), 255, 255), // Random bright hue (full saturation & value)
|
||||
CHSV( random8(), 128, 255), // Random pastel (medium saturation, full value)
|
||||
CHSV( random8(), 255, 255)); // Another random bright hue
|
||||
|
||||
// The CRGBPalette16 constructor automatically creates a 16-color gradient
|
||||
// between these four colors, evenly distributed
|
||||
}
|
||||
|
||||
|
||||
// This function sets up a palette of black and white stripes,
|
||||
// using code. Since the palette is effectively an array of
|
||||
// sixteen CRGB colors, the various fill_* functions can be used
|
||||
// to set them up.
|
||||
void SetupBlackAndWhiteStripedPalette()
|
||||
{
|
||||
// 'black out' all 16 palette entries...
|
||||
fill_solid( currentPalette, 16, CRGB::Black);
|
||||
|
||||
// and set every fourth one to white to create stripes
|
||||
// Positions 0, 4, 8, and 12 in the 16-color palette
|
||||
currentPalette[0] = CRGB::White;
|
||||
currentPalette[4] = CRGB::White;
|
||||
currentPalette[8] = CRGB::White;
|
||||
currentPalette[12] = CRGB::White;
|
||||
|
||||
// The palette interpolation will create smooth transitions between these colors
|
||||
}
|
||||
|
||||
// This function sets up a palette of purple and green stripes.
|
||||
void SetupPurpleAndGreenPalette()
|
||||
{
|
||||
// Define our colors using HSV color space for consistency
|
||||
CRGB purple = CHSV( HUE_PURPLE, 255, 255); // Bright purple
|
||||
CRGB green = CHSV( HUE_GREEN, 255, 255); // Bright green
|
||||
CRGB black = CRGB::Black; // Black
|
||||
|
||||
// Create a 16-color palette with a specific pattern:
|
||||
// green-green-black-black-purple-purple-black-black, repeated twice
|
||||
// This creates alternating green and purple stripes with black in between
|
||||
currentPalette = CRGBPalette16(
|
||||
green, green, black, black, // First 4 colors
|
||||
purple, purple, black, black, // Next 4 colors
|
||||
green, green, black, black, // Repeat the pattern
|
||||
purple, purple, black, black ); // Last 4 colors
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Mark's xy coordinate mapping code. See the XYMatrix for more information on it.
|
||||
//
|
||||
// This function converts x,y coordinates to a single array index
|
||||
// It handles both regular and serpentine matrix layouts
|
||||
uint16_t XY( uint8_t x, uint8_t y)
|
||||
{
|
||||
uint16_t i;
|
||||
|
||||
// For a regular/sequential layout, it's just y * width + x
|
||||
if( kMatrixSerpentineLayout == false) {
|
||||
i = (y * kMatrixWidth) + x;
|
||||
}
|
||||
|
||||
// For a serpentine layout (zigzag), odd rows run backwards
|
||||
if( kMatrixSerpentineLayout == true) {
|
||||
if( y & 0x01) { // Check if y is odd (bitwise AND with 1)
|
||||
// Odd rows run backwards
|
||||
uint8_t reverseX = (kMatrixWidth - 1) - x;
|
||||
i = (y * kMatrixWidth) + reverseX;
|
||||
} else {
|
||||
// Even rows run forwards
|
||||
i = (y * kMatrixWidth) + x;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
#include "NoisePlusPalette.h"
|
||||
#endif // End of the non-AVR code section
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
/// @file Overclock.ino
|
||||
/// @brief Demonstrates how to overclock a FastLED setup
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// To effectively test the overclock feature we need
|
||||
// a large enough dataset to test against. Unfortunately
|
||||
// the avr platforms don't have enough memory so this example
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@
|
|||
// Animated, ever-changing rainbows.
|
||||
// by Mark Kriegsman
|
||||
|
||||
#if FASTLED_VERSION < 3001000
|
||||
#error "Requires FastLED 3.1 or later; check github for latest code."
|
||||
#endif
|
||||
|
||||
#define DATA_PIN 3
|
||||
//#define CLK_PIN 4
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
This is a 1D wave simluation!
|
||||
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
@ -27,7 +32,6 @@ all the UI elements you see below.
|
|||
#include "src/xypaths.h"
|
||||
#include "fl/function.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
using namespace fl;
|
||||
|
||||
|
|
@ -37,8 +41,6 @@ using namespace fl;
|
|||
#define IS_SERPINTINE true
|
||||
#define TIME_ANIMATION 1000 // ms
|
||||
|
||||
// CRGB leds[NUM_LEDS];
|
||||
|
||||
LedsXY<WIDTH, HEIGHT> leds;
|
||||
XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE);
|
||||
UITitle title("Simple control of an xy path");
|
||||
|
|
@ -49,31 +51,21 @@ UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f);
|
|||
UISlider steps("Steps", 100.0f, 1.0f, 200.0f, 1.0f);
|
||||
UISlider length("Length", 1.0f, 0.0f, 1.0f, 0.01f);
|
||||
|
||||
|
||||
XYPathPtr heartPath = XYPath::NewHeartPath(WIDTH, HEIGHT);
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
auto screenmap = xyMap.toScreenMap();
|
||||
screenmap.setDiameter(.2);
|
||||
FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS).setScreenMap(screenmap);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
fl::clear(leds);
|
||||
|
||||
// FASTLED_ASSERT(false, "This is a test");
|
||||
|
||||
// leds(x,y) = CRGB(255, 0, 0);
|
||||
float from = offset;
|
||||
float to = length.value() + offset.value();
|
||||
|
||||
heartPath->drawColor(CRGB(255, 0, 0), from, to, &leds, steps.as_int());
|
||||
|
||||
|
||||
FastLED.show();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
/*
|
||||
This demo is best viewed using the FastLED compiler.
|
||||
|
||||
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
|
||||
|
||||
Python
|
||||
|
||||
Install: pip install fastled
|
||||
Run: fastled <this sketch directory>
|
||||
This will compile and preview the sketch in the browser, and enable
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"type": "git",
|
||||
"url": "https://github.com/FastLED/FastLED.git"
|
||||
},
|
||||
"version": "3.9.20",
|
||||
"version": "3.10.1",
|
||||
"license": "MIT",
|
||||
"homepage": "http://fastled.io",
|
||||
"frameworks": "arduino",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name=FastLED
|
||||
version=3.9.20
|
||||
version=3.10.1
|
||||
author=Daniel Garcia
|
||||
maintainer=Daniel Garcia <dgarcia@fastled.io>
|
||||
sentence=Multi-platform library for controlling dozens of different types of LEDs along with optimized math, effect, and noise functions.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ default_envs = dev
|
|||
|
||||
[env:generic-esp]
|
||||
# Developement branch of the open source espressif32 platform
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip
|
||||
framework = arduino
|
||||
upload_protocol = esptool
|
||||
monitor_filters =
|
||||
|
|
@ -41,9 +41,6 @@ extends = env:generic-esp
|
|||
board = seeed_xiao_esp32s3
|
||||
build_flags =
|
||||
${env:generic-esp.build_flags}
|
||||
-DBOARD_HAS_PSRAM
|
||||
-mfix-esp32-psram-cache-issue
|
||||
-mfix-esp32-psram-cache-strategy=memw
|
||||
board_build.partitions = huge_app.csv
|
||||
|
||||
[env:esp32c6]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,18 @@
|
|||
FastLED 3.9.18 + 3.9.19
|
||||
FastLED 3.10.0
|
||||
==============
|
||||
|
||||
* Animartrix now out of beta.
|
||||
* examples/Animartrix/Animartrix.ino
|
||||
* ESP32
|
||||
* Esp32P4 now officially supported.
|
||||
* ESP32-S3 I2S driver is improved
|
||||
* It will now auto error on known bad Esp32-Arduino Core versions.
|
||||
* Arudino core 3.2.0 is now know to work.
|
||||
* Documentation has been greatly simplified and unnecessary steps have been removed.
|
||||
|
||||
|
||||
|
||||
FastLED 3.9.18 + 3.9.19 + 3.9.20
|
||||
==============
|
||||
* Hotfixes for AVR platforms for 3.9.17
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ uint8_t get_brightness();
|
|||
void *pSmartMatrix = NULL;
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(global-constructors)
|
||||
FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
|
||||
CFastLED FastLED; // global constructor allowed in this case.
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@
|
|||
/// * 1 digit for the major version
|
||||
/// * 3 digits for the minor version
|
||||
/// * 3 digits for the patch version
|
||||
#define FASTLED_VERSION 3009020
|
||||
#define FASTLED_VERSION 301001
|
||||
#ifndef FASTLED_INTERNAL
|
||||
# ifdef FASTLED_SHOW_VERSION
|
||||
# ifdef FASTLED_HAS_PRAGMA_MESSAGE
|
||||
# pragma message "FastLED version 3.009.020"
|
||||
# pragma message "FastLED version 3.010.001"
|
||||
# else
|
||||
# warning FastLED version 3.009.020 (Not really a warning, just telling you here.)
|
||||
# warning FastLED version 3.010.001 (Not really a warning, just telling you here.)
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
|
@ -968,3 +968,6 @@ using namespace fl;
|
|||
void loop() { FASTLED_WARN("hijacked the loop"); real_loop(); } \
|
||||
void real_loop()
|
||||
#endif
|
||||
|
||||
|
||||
#include "fl/sketch_macros.h"
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
#include "FastLED.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
#include "fl/bilinear_expansion.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/downscale.h"
|
||||
#include "lib8tion/math8.h"
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ void CRGB::upscale(const CRGB *src, const fl::XYMap &srcXY, CRGB *dst,
|
|||
"Upscaling only works with a src matrix that is rectangular");
|
||||
uint16_t w = srcXY.getWidth();
|
||||
uint16_t h = srcXY.getHeight();
|
||||
fl::bilinearExpand(src, dst, w, h, dstXY);
|
||||
fl::upscale(src, dst, w, h, dstXY);
|
||||
}
|
||||
|
||||
CRGB &CRGB::nscale8(uint8_t scaledown) {
|
||||
|
|
|
|||
|
|
@ -11,4 +11,159 @@ void reverse(Iterator first, Iterator last) {
|
|||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
Iterator max_element(Iterator first, Iterator last) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator max_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (*max_iter < *first) {
|
||||
max_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return max_iter;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator max_element(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator max_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (comp(*max_iter, *first)) {
|
||||
max_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return max_iter;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
Iterator min_element(Iterator first, Iterator last) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator min_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (*first < *min_iter) {
|
||||
min_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return min_iter;
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Compare>
|
||||
Iterator min_element(Iterator first, Iterator last, Compare comp) {
|
||||
if (first == last) {
|
||||
return last;
|
||||
}
|
||||
|
||||
Iterator min_iter = first;
|
||||
++first;
|
||||
|
||||
while (first != last) {
|
||||
if (comp(*first, *min_iter)) {
|
||||
min_iter = first;
|
||||
}
|
||||
++first;
|
||||
}
|
||||
|
||||
return min_iter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename Iterator1, typename Iterator2>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2) {
|
||||
while (first1 != last1) {
|
||||
if (*first1 != *first2) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2, typename BinaryPredicate>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryPredicate pred) {
|
||||
while (first1 != last1) {
|
||||
if (!pred(*first1, *first2)) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2) {
|
||||
while (first1 != last1 && first2 != last2) {
|
||||
if (*first1 != *first2) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return first1 == last1 && first2 == last2;
|
||||
}
|
||||
|
||||
template <typename Iterator1, typename Iterator2, typename BinaryPredicate>
|
||||
bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, BinaryPredicate pred) {
|
||||
while (first1 != last1 && first2 != last2) {
|
||||
if (!pred(*first1, *first2)) {
|
||||
return false;
|
||||
}
|
||||
++first1;
|
||||
++first2;
|
||||
}
|
||||
return first1 == last1 && first2 == last2;
|
||||
}
|
||||
|
||||
template <typename Container1, typename Container2>
|
||||
bool equal_container(const Container1& c1, const Container2& c2) {
|
||||
size_t size1 = c1.size();
|
||||
size_t size2 = c2.size();
|
||||
if (size1 != size2) {
|
||||
return false;
|
||||
}
|
||||
return equal(c1.begin(), c1.end(), c2.begin(), c2.end());
|
||||
}
|
||||
|
||||
template <typename Container1, typename Container2, typename BinaryPredicate>
|
||||
bool equal_container(const Container1& c1, const Container2& c2, BinaryPredicate pred) {
|
||||
size_t size1 = c1.size();
|
||||
size_t size2 = c2.size();
|
||||
if (size1 != size2) {
|
||||
return false;
|
||||
}
|
||||
return equal(c1.begin(), c1.end(), c2.begin(), c2.end(), pred);
|
||||
}
|
||||
|
||||
|
||||
template <typename Iterator, typename T>
|
||||
void fill(Iterator first, Iterator last, const T& value) {
|
||||
while (first != last) {
|
||||
*first = value;
|
||||
++first;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ void swap(array<T, N> &lhs,
|
|||
TYPE *NAME = reinterpret_cast<TYPE *>(alloca(sizeof(TYPE) * (SIZE))); \
|
||||
memset(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
#elif __has_include(<cstdlib>)
|
||||
#include <cstdlib>
|
||||
#include <cstdlib> // ok include
|
||||
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \
|
||||
TYPE *NAME = reinterpret_cast<TYPE *>(alloca(sizeof(TYPE) * (SIZE))); \
|
||||
memset(NAME, 0, sizeof(TYPE) * (SIZE))
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/deprecated.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
namespace fl {
|
||||
|
|
@ -19,47 +21,62 @@ namespace fl {
|
|||
/// @param inputHeight The height of the input grid.
|
||||
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
|
||||
/// pixel is mapped outside of the range then it is clipped.
|
||||
|
||||
void bilinearExpandArbitrary(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
fl::XYMap xyMap);
|
||||
fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscaleArbitrary from upscale.h");
|
||||
|
||||
/// @brief Performs bilinear interpolation for upscaling an image.
|
||||
/// @param output The output grid to write into the interpolated values.
|
||||
/// @param input The input grid to read from.
|
||||
/// @param inputWidth The width of the input grid.
|
||||
/// @param inputHeight The height of the input grid.
|
||||
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
|
||||
/// pixel is mapped outside of the range then it is clipped.
|
||||
void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, fl::XYMap xyMap);
|
||||
uint8_t inputHeight, fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscalePowerOf2 from upscale.h");
|
||||
|
||||
//
|
||||
inline void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap) {
|
||||
uint16_t outputWidth = xyMap.getWidth();
|
||||
uint16_t outputHeight = xyMap.getHeight();
|
||||
const bool wontFit =
|
||||
(outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight());
|
||||
// if the input dimensions are not a power of 2 then we can't use the
|
||||
// optimized version.
|
||||
if (wontFit || (inputWidth & (inputWidth - 1)) ||
|
||||
(inputHeight & (inputHeight - 1))) {
|
||||
bilinearExpandArbitrary(input, output, inputWidth, inputHeight, xyMap);
|
||||
} else {
|
||||
bilinearExpandPowerOf2(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
}
|
||||
void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscale from upscale.h");
|
||||
|
||||
// These are here for testing purposes and are slow. Their primary use
|
||||
// is to test against the fixed integer version above.
|
||||
void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, fl::XYMap xyMap);
|
||||
uint8_t inputHeight, fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscaleFloat from upscale.h");
|
||||
|
||||
void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
fl::XYMap xyMap);
|
||||
fl::XYMap xyMap)
|
||||
FASTLED_DEPRECATED("use upscaleArbitraryFloat from upscale.h");
|
||||
|
||||
uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, float dx, float dy);
|
||||
uint8_t v11, float dx, float dy)
|
||||
FASTLED_DEPRECATED("use upscaleFloat from upscale.h");
|
||||
|
||||
////////////////// Inline definitions for backward compatibility ///////////////////////
|
||||
|
||||
inline void bilinearExpandArbitrary(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
fl::XYMap xyMap) {
|
||||
upscaleArbitrary(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
|
||||
inline void bilinearExpandPowerOf2(const CRGB *input, CRGB *output,
|
||||
uint8_t inputWidth, uint8_t inputHeight,
|
||||
fl::XYMap xyMap) {
|
||||
upscalePowerOf2(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
|
||||
inline void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap) {
|
||||
upscale(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
|
||||
inline void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth,
|
||||
uint16_t inputHeight,
|
||||
fl::XYMap xyMap) {
|
||||
upscaleArbitraryFloat(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
|
||||
inline uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, float dx, float dy) {
|
||||
return upscaleFloat(v00, v10, v01, v11, dx, dy);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@
|
|||
#include "fl/leds.h"
|
||||
#include "fl/stdint.h"
|
||||
|
||||
|
||||
namespace fl {
|
||||
|
||||
template<typename T>
|
||||
class Grid;
|
||||
|
||||
// Memory safe clear function for CRGB arrays.
|
||||
template <int N> inline void clear(CRGB (&arr)[N]) {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
|
|
@ -18,4 +23,17 @@ inline void clear(LedsXY<W, H> &leds) {
|
|||
leds.fill(CRGB::Black);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void clear(Grid<T> &grid) {
|
||||
grid.clear();
|
||||
}
|
||||
|
||||
// Default, when you don't know what do then call clear.
|
||||
template<typename Container>
|
||||
inline void clear(Container &container) {
|
||||
container.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace fl
|
||||
|
|
@ -19,3 +19,11 @@
|
|||
#define FL_DISABLE_WARNING_POP
|
||||
#define FL_DISABLE_WARNING(warning)
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__clang__)
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS \
|
||||
FL_DISABLE_WARNING(global-constructors)
|
||||
#else
|
||||
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS /* nothing */
|
||||
#endif
|
||||
|
|
@ -1,138 +1,142 @@
|
|||
#include "fl/corkscrew.h"
|
||||
#include "fl/algorithm.h"
|
||||
#include "fl/assert.h"
|
||||
#include "fl/math.h"
|
||||
#include "fl/splat.h"
|
||||
|
||||
#include "fl/math_macros.h"
|
||||
|
||||
#define TWO_PI (PI * 2.0)
|
||||
|
||||
namespace fl {
|
||||
|
||||
void generateMap(const Corkscrew::Input &input, CorkscrewOutput &output);
|
||||
void generateState(const Corkscrew::Input &input, CorkscrewState *output);
|
||||
|
||||
void generateMap(const Corkscrew::Input &input, CorkscrewOutput &output) {
|
||||
// Calculate circumference per turn from height and total angle
|
||||
float circumferencePerTurn = input.totalHeight * TWO_PI / input.totalAngle;
|
||||
void generateState(const Corkscrew::Input &input, CorkscrewState *output) {
|
||||
|
||||
// Calculate vertical segments based on number of turns
|
||||
// For a single turn (2π), we want exactly 1 vertical segment
|
||||
// For two turns (4π), we want exactly 2 vertical segments
|
||||
uint16_t verticalSegments = round(input.totalAngle / TWO_PI);
|
||||
// uint16_t verticalSegments = ceil(input.totalTurns);
|
||||
|
||||
// Calculate width based on LED density per turn
|
||||
float ledsPerTurn = static_cast<float>(input.numLeds) / verticalSegments;
|
||||
// float ledsPerTurn = static_cast<float>(input.numLeds) / verticalSegments;
|
||||
|
||||
// Determine cylindrical dimensions
|
||||
output.height = verticalSegments;
|
||||
output.width = ceil(ledsPerTurn);
|
||||
|
||||
output.mapping.clear();
|
||||
output->mapping.clear();
|
||||
output->width = 0; // we will change this below.
|
||||
output->height = 0;
|
||||
|
||||
// If numLeds is specified, use that for mapping size instead of grid
|
||||
if (input.numLeds > 0) {
|
||||
output.mapping.reserve(input.numLeds);
|
||||
output->mapping.reserve(input.numLeds);
|
||||
// Generate LED mapping based on numLeds
|
||||
// Note that width_step should be 1.0f/float(input.numLeds) so last led in a
|
||||
// turn does not wrap around.
|
||||
const float width_step =
|
||||
1.0f / float(input.numLeds); // Corkscrew reaches max width on last led.
|
||||
const float height_step =
|
||||
1.0f /
|
||||
float(input.numLeds - 1); // Corkscrew reaches max height on last led.
|
||||
// const float led_width_factor = circumferencePerTurn / TWO_PI;
|
||||
const float length_per_turn = input.numLeds / input.totalTurns;
|
||||
|
||||
// Generate LED mapping based on numLeds
|
||||
for (uint16_t i = 0; i < input.numLeds; ++i) {
|
||||
// Calculate position along the corkscrew (0.0 to 1.0)
|
||||
float position = static_cast<float>(i) / (input.numLeds - 1);
|
||||
for (uint16_t i = 0; i < input.numLeds; ++i) {
|
||||
// Calculate position along the corkscrew (0.0 to 1.0)
|
||||
const float i_f = static_cast<float>(i);
|
||||
const float alpha_width = i_f * width_step;
|
||||
const float alpha_height = i_f * height_step;
|
||||
const float width_before_mod = alpha_width * input.totalLength;
|
||||
const float height = alpha_height * input.totalHeight;
|
||||
const float width = fmodf(width_before_mod, length_per_turn);
|
||||
output->mapping.push_back({width, height});
|
||||
}
|
||||
|
||||
// Calculate angle and height
|
||||
float angle = position * input.totalAngle;
|
||||
float height = position * verticalSegments;
|
||||
|
||||
// Calculate circumference position
|
||||
float circumference = fmodf(angle * circumferencePerTurn / TWO_PI,
|
||||
circumferencePerTurn);
|
||||
|
||||
// Store the mapping
|
||||
output.mapping.push_back({circumference, height});
|
||||
}
|
||||
} else {
|
||||
// Original grid-based mapping
|
||||
output.mapping.reserve(output.width * output.height);
|
||||
|
||||
// Corrected super sampling step size
|
||||
float thetaStep = 0.5f / output.width;
|
||||
float hStep = 0.5f / output.height;
|
||||
|
||||
// Precompute angle per segment
|
||||
float anglePerSegment = input.totalAngle / verticalSegments;
|
||||
|
||||
// Loop over cylindrical pixels
|
||||
for (uint16_t h = 0; h < output.height; ++h) {
|
||||
float segmentOffset = input.offsetCircumference * h;
|
||||
for (uint16_t w = 0; w < output.width; ++w) {
|
||||
vec2f sample = {0, 0};
|
||||
// 2x2 supersampling
|
||||
for (uint8_t ssH = 0; ssH < 2; ++ssH) {
|
||||
for (uint8_t ssW = 0; ssW < 2; ++ssW) {
|
||||
float theta =
|
||||
(w + 0.5f + ssW * thetaStep) / output.width;
|
||||
float height = (h + 0.5f + ssH * hStep) / output.height;
|
||||
|
||||
// Corkscrew projection (θ,h)
|
||||
float corkscrewTheta =
|
||||
theta * TWO_PI + anglePerSegment * h;
|
||||
float corkscrewH = height * verticalSegments;
|
||||
|
||||
// Apply circumference offset
|
||||
float corkscrewCircumference = fmodf(
|
||||
corkscrewTheta * circumferencePerTurn / TWO_PI +
|
||||
segmentOffset,
|
||||
circumferencePerTurn);
|
||||
|
||||
// Accumulate samples
|
||||
sample.x += corkscrewCircumference;
|
||||
sample.y += corkscrewH;
|
||||
}
|
||||
}
|
||||
|
||||
// Average the supersampled points
|
||||
sample.x *= 0.25f;
|
||||
sample.y *= 0.25f;
|
||||
|
||||
output.mapping.push_back(sample);
|
||||
}
|
||||
if (!output->mapping.empty()) {
|
||||
float max_width = 0.0f;
|
||||
float max_height = 0.0f;
|
||||
for (const auto &point : output->mapping) {
|
||||
max_width = MAX(max_width, point.x);
|
||||
max_height = MAX(max_height, point.y);
|
||||
}
|
||||
output->width = static_cast<uint16_t>(ceilf(max_width)) + 1;
|
||||
output->height = static_cast<uint16_t>(ceilf(max_height)) + 1;
|
||||
}
|
||||
|
||||
// Apply inversion if requested
|
||||
if (input.invert) {
|
||||
fl::reverse(output.mapping.begin(), output.mapping.end());
|
||||
fl::reverse(output->mapping.begin(), output->mapping.end());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Corkscrew::Corkscrew(const Corkscrew::Input &input) : mInput(input) {
|
||||
fl::generateMap(mInput, mOutput);
|
||||
fl::generateState(mInput, &mState);
|
||||
}
|
||||
|
||||
vec2f Corkscrew::at(uint16_t i) const {
|
||||
if (i >= mOutput.mapping.size()) {
|
||||
vec2f Corkscrew::at_exact(uint16_t i) const {
|
||||
if (i >= mState.mapping.size()) {
|
||||
// Handle out-of-bounds access, possibly by returning a default value
|
||||
return vec2f(0, 0);
|
||||
}
|
||||
// Convert the float position to integer
|
||||
const vec2f &position = mOutput.mapping[i];
|
||||
const vec2f &position = mState.mapping[i];
|
||||
return position;
|
||||
}
|
||||
|
||||
Tile2x2_u8 Corkscrew::at_splat(uint16_t i) const {
|
||||
if (i >= mOutput.mapping.size()) {
|
||||
|
||||
Tile2x2_u8 Corkscrew::at_splat_extrapolate(float i) const {
|
||||
// To finish this, we need to handle wrap around.
|
||||
// To accomplish this we need a different data structure than the the
|
||||
// Tile2x2_u8.
|
||||
// 1. It will be called CorkscrewTile2x2_u8.
|
||||
// 2. The four alpha values will each contain the index the LED is at,
|
||||
// uint16_t.
|
||||
// 3. There will be no origin, each pixel in the tile will contain a
|
||||
// uint16_t origin. This is not supposed to be a storage format, but a
|
||||
// convenient pre-computed value for rendering.
|
||||
if (i >= mState.mapping.size()) {
|
||||
// Handle out-of-bounds access, possibly by returning a default
|
||||
// Tile2x2_u8
|
||||
FASTLED_ASSERT(false, "Out of bounds access in Corkscrew at_splat: "
|
||||
<< i << " size: " << mState.mapping.size());
|
||||
return Tile2x2_u8();
|
||||
}
|
||||
// Use the splat function to convert the vec2f to a Tile2x2_u8
|
||||
return splat(mOutput.mapping[i]);
|
||||
float i_floor = floorf(i);
|
||||
float i_ceil = ceilf(i);
|
||||
if (ALMOST_EQUAL_FLOAT(i_floor, i_ceil)) {
|
||||
// If the index is the same, just return the splat of that index
|
||||
return splat(mState.mapping[static_cast<uint16_t>(i_floor)]);
|
||||
} else {
|
||||
// Interpolate between the two points and return the splat of the result
|
||||
vec2f pos1 = mState.mapping[static_cast<uint16_t>(i_floor)];
|
||||
vec2f pos2 = mState.mapping[static_cast<uint16_t>(i_ceil)];
|
||||
|
||||
if (pos2.x < pos1.x) {
|
||||
// If the next point is on the other side of the cylinder, we need
|
||||
// to wrap it around and bring it back into the positive direction so we can construct a Tile2x2_u8 wrap with it.
|
||||
pos2.x += mState.width;
|
||||
}
|
||||
|
||||
vec2f interpolated_pos =
|
||||
pos1 * (1.0f - (i - i_floor)) + pos2 * (i - i_floor);
|
||||
return splat(interpolated_pos);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Corkscrew::size() const { return mOutput.mapping.size(); }
|
||||
size_t Corkscrew::size() const { return mState.mapping.size(); }
|
||||
|
||||
CorkscrewOutput Corkscrew::generateMap(const Input &input) {
|
||||
CorkscrewOutput output;
|
||||
fl::generateMap(input, output);
|
||||
Corkscrew::State Corkscrew::generateState(const Corkscrew::Input &input) {
|
||||
CorkscrewState output;
|
||||
fl::generateState(input, &output);
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
||||
|
||||
Tile2x2_u8_wrap Corkscrew::at_wrap(float i) const {
|
||||
// This is a splatted pixel, but wrapped around the cylinder.
|
||||
// This is useful for rendering the corkscrew in a cylindrical way.
|
||||
Tile2x2_u8 tile = at_splat_extrapolate(i);
|
||||
return Tile2x2_u8_wrap(tile, mState.width, mState.height);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
|
@ -4,15 +4,15 @@
|
|||
* @file corkscrew.h
|
||||
* @brief Corkscrew projection utilities
|
||||
*
|
||||
* Corkscrew projection maps from Corkscrew (θ, h) to Cylindrical cartesian (w,
|
||||
* h) space, where w = one turn of the Corkscrew. The corkscrew at (0,0) will
|
||||
* map to (0,0) in cylindrical space.
|
||||
* You want to draw on a rectangular surface, and have it map to a GOD DAMN
|
||||
* CORKSCREW! Well guess what, this is the file for you.
|
||||
*
|
||||
* Corkscrew projection maps from Corkscrew angle height, (θ, h) to Cylindrical
|
||||
* cartesian (w, h) space, where w = one turn of the Corkscrew. The corkscrew at
|
||||
* (0) will map to the first index in the cylinder map at (0, 0). The last value
|
||||
* is probly not at the max pixel value at (width - 1, height - 1), but could
|
||||
* be.
|
||||
*
|
||||
* The projection:
|
||||
* - Super samples cylindrical space?
|
||||
* - θ is normalized to [0, 1] or mapped to [0, W-1] for grid projection
|
||||
* - Uses 2x2 super sampling for better visual quality
|
||||
* - Works with XYPathRenderer's "Splat Rendering" for sub-pixel rendering
|
||||
*
|
||||
* Inputs:
|
||||
* - Total Height of the Corkscrew in centimeters
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
#include "fl/allocator.h"
|
||||
#include "fl/geometry.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/tile2x2.h"
|
||||
#include "fl/vector.h"
|
||||
|
||||
|
|
@ -46,41 +47,43 @@ namespace fl {
|
|||
* @return The resulting cylindrical mapping.
|
||||
*/
|
||||
struct CorkscrewInput {
|
||||
float totalLength = 100; // Total length of the corkscrew in centimeters,
|
||||
// set to dense 144 strips.
|
||||
float totalHeight = 23.25; // Total height of the corkscrew in centimeters
|
||||
// for 144 densly wrapped up over 19 turns
|
||||
float totalAngle = 19.f * 2 * PI; // Default to 19 turns
|
||||
float offsetCircumference = 0; // Optional offset for gap accounting
|
||||
uint16_t numLeds = 144; // Default to dense 144 leds.
|
||||
bool invert = false; // If true, reverse the mapping order
|
||||
float totalTurns = 19.f; // Default to 19 turns
|
||||
float offsetCircumference = 0; // Optional offset for gap accounting
|
||||
uint16_t numLeds = 144; // Default to dense 144 leds.
|
||||
bool invert = false; // If true, reverse the mapping order
|
||||
CorkscrewInput() = default;
|
||||
CorkscrewInput(float height, float total_angle, float offset = 0,
|
||||
uint16_t leds = 144, bool invertMapping = false)
|
||||
: totalHeight(height), totalAngle(total_angle),
|
||||
offsetCircumference(offset), numLeds(leds), invert(invertMapping) {}
|
||||
CorkscrewInput(float total_length, float height, float total_turns,
|
||||
uint16_t leds, float offset = 0,
|
||||
bool invertMapping = false)
|
||||
: totalLength(total_length), totalHeight(height),
|
||||
totalTurns(total_turns), offsetCircumference(offset), numLeds(leds),
|
||||
invert(invertMapping) {}
|
||||
};
|
||||
|
||||
struct CorkscrewOutput {
|
||||
struct CorkscrewState {
|
||||
uint16_t width = 0; // Width of cylindrical map (circumference of one turn)
|
||||
uint16_t height = 0; // Height of cylindrical map (total vertical segments)
|
||||
fl::vector<fl::vec2f, fl::allocator_psram<fl::vec2f>>
|
||||
mapping; // Full precision mapping from corkscrew to cylindrical
|
||||
CorkscrewOutput() = default;
|
||||
CorkscrewState() = default;
|
||||
|
||||
class iterator {
|
||||
public:
|
||||
public:
|
||||
using value_type = vec2f;
|
||||
using difference_type = int32_t;
|
||||
using pointer = vec2f*;
|
||||
using reference = vec2f&;
|
||||
using pointer = vec2f *;
|
||||
using reference = vec2f &;
|
||||
|
||||
iterator(CorkscrewOutput* owner, size_t position)
|
||||
iterator(CorkscrewState *owner, size_t position)
|
||||
: owner_(owner), position_(position) {}
|
||||
|
||||
vec2f& operator*() const {
|
||||
return owner_->mapping[position_];
|
||||
}
|
||||
vec2f &operator*() const { return owner_->mapping[position_]; }
|
||||
|
||||
iterator& operator++() {
|
||||
iterator &operator++() {
|
||||
++position_;
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -91,67 +94,82 @@ struct CorkscrewOutput {
|
|||
return temp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator& other) const {
|
||||
iterator &operator--() {
|
||||
--position_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) {
|
||||
iterator temp = *this;
|
||||
--position_;
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const {
|
||||
return position_ == other.position_;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& other) const {
|
||||
bool operator!=(const iterator &other) const {
|
||||
return position_ != other.position_;
|
||||
}
|
||||
|
||||
private:
|
||||
CorkscrewOutput* owner_;
|
||||
difference_type operator-(const iterator &other) const {
|
||||
return static_cast<difference_type>(position_) -
|
||||
static_cast<difference_type>(other.position_);
|
||||
}
|
||||
|
||||
private:
|
||||
CorkscrewState *owner_;
|
||||
size_t position_;
|
||||
};
|
||||
|
||||
iterator begin() {
|
||||
return iterator(this, 0);
|
||||
}
|
||||
iterator begin() { return iterator(this, 0); }
|
||||
|
||||
iterator end() {
|
||||
return iterator(this, mapping.size());
|
||||
}
|
||||
fl::Tile2x2_u8 at(int16_t x, int16_t y) const;
|
||||
iterator end() { return iterator(this, mapping.size()); }
|
||||
};
|
||||
|
||||
// Maps a Corkscrew defined by the input to a cylindrical mapping for rendering
|
||||
// a densly wrapped LED corkscrew.
|
||||
class Corkscrew {
|
||||
public:
|
||||
using Input = CorkscrewInput;
|
||||
using Output = CorkscrewOutput;
|
||||
using iterator = CorkscrewOutput::iterator;
|
||||
using State = CorkscrewState;
|
||||
using iterator = CorkscrewState::iterator;
|
||||
|
||||
Corkscrew(const Input &input);
|
||||
Corkscrew(const Corkscrew &) = default;
|
||||
Corkscrew(Corkscrew &&) = default;
|
||||
|
||||
vec2f at_exact(uint16_t i) const;
|
||||
|
||||
// This is the future api.
|
||||
Tile2x2_u8_wrap at_wrap(float i) const;
|
||||
|
||||
vec2f at(uint16_t i) const;
|
||||
// This is a splatted pixel. This is will look way better than
|
||||
// using at(), because it uses 2x2 neighboor sampling.
|
||||
Tile2x2_u8 at_splat(uint16_t i) const;
|
||||
size_t size() const;
|
||||
|
||||
iterator begin() {
|
||||
return mOutput.begin();
|
||||
}
|
||||
iterator begin() { return mState.begin(); }
|
||||
|
||||
iterator end() {
|
||||
return mOutput.end();
|
||||
}
|
||||
iterator end() { return mState.end(); }
|
||||
|
||||
/// For testing
|
||||
|
||||
static CorkscrewOutput generateMap(const Input &input);
|
||||
static State generateState(const Input &input);
|
||||
|
||||
Output& access() {
|
||||
return mOutput;
|
||||
}
|
||||
State &access() { return mState; }
|
||||
|
||||
const Output& access() const {
|
||||
return mOutput;
|
||||
}
|
||||
const State &access() const { return mState; }
|
||||
|
||||
int16_t cylinder_width() const { return mState.width; }
|
||||
int16_t cylinder_height() const { return mState.height; }
|
||||
|
||||
private:
|
||||
Input mInput; // The input parameters defining the corkscrew
|
||||
CorkscrewOutput mOutput; // The resulting cylindrical mapping
|
||||
// For internal use. Splats the pixel on the surface which
|
||||
// extends past the width. This extended Tile2x2 is designed
|
||||
// to be wrapped around with a Tile2x2_u8_wrap.
|
||||
Tile2x2_u8 at_splat_extrapolate(float i) const;
|
||||
|
||||
Input mInput; // The input parameters defining the corkscrew
|
||||
State mState; // The resulting cylindrical mapping
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -13,19 +13,15 @@ template <typename T> class Grid {
|
|||
Grid(uint32_t width, uint32_t height) { reset(width, height); }
|
||||
|
||||
void reset(uint32_t width, uint32_t height) {
|
||||
clear();
|
||||
if (width != mWidth || height != mHeight) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
// Only re-allocate if the size is now bigger.
|
||||
mData.reserve(width * height);
|
||||
// Fill with default objects.
|
||||
while (mData.size() < width * height) {
|
||||
mData.push_back(T());
|
||||
}
|
||||
mSlice = fl::MatrixSlice<T>(mData.data(), width, height, 0, 0,
|
||||
width - 1, height - 1);
|
||||
mData.resize(width * height);
|
||||
|
||||
}
|
||||
clear();
|
||||
mSlice = fl::MatrixSlice<T>(mData.data(), width, height, 0, 0,
|
||||
width, height);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
|
|
@ -60,6 +56,11 @@ template <typename T> class Grid {
|
|||
uint32_t width() const { return mWidth; }
|
||||
uint32_t height() const { return mHeight; }
|
||||
|
||||
T* data() { return mData.data(); }
|
||||
const T* data() const { return mData.data(); }
|
||||
|
||||
size_t size() const { return mData.size(); }
|
||||
|
||||
private:
|
||||
static T &NullValue() {
|
||||
static T gNull;
|
||||
|
|
|
|||
44
libraries/FastLED/src/fl/range_access.h
Normal file
44
libraries/FastLED/src/fl/range_access.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
// fl::begin for arrays
|
||||
template <typename T, size_t N>
|
||||
constexpr T* begin(T (&array)[N]) noexcept {
|
||||
return array;
|
||||
}
|
||||
|
||||
// fl::end for arrays
|
||||
template <typename T, size_t N>
|
||||
constexpr T* end(T (&array)[N]) noexcept {
|
||||
return array + N;
|
||||
}
|
||||
|
||||
// fl::begin for containers with begin() member function
|
||||
template <typename Container>
|
||||
constexpr auto begin(Container& c) -> decltype(c.begin()) {
|
||||
return c.begin();
|
||||
}
|
||||
|
||||
// fl::begin for const containers with begin() member function
|
||||
template <typename Container>
|
||||
constexpr auto begin(const Container& c) -> decltype(c.begin()) {
|
||||
return c.begin();
|
||||
}
|
||||
|
||||
// fl::end for containers with end() member function
|
||||
template <typename Container>
|
||||
constexpr auto end(Container& c) -> decltype(c.end()) {
|
||||
return c.end();
|
||||
}
|
||||
|
||||
// fl::end for const containers with end() member function
|
||||
template <typename Container>
|
||||
constexpr auto end(const Container& c) -> decltype(c.end()) {
|
||||
return c.end();
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
||||
|
|
@ -9,6 +9,8 @@ namespace fl {
|
|||
template <typename T, int N = 0> class Singleton {
|
||||
public:
|
||||
static T &instance() {
|
||||
// We love function level singletons!! They don't get construction until first call.
|
||||
// And they seem to have locks on them in most compilers. So yay.
|
||||
static T instance;
|
||||
return instance;
|
||||
}
|
||||
|
|
|
|||
7
libraries/FastLED/src/fl/sketch_macros.h
Normal file
7
libraries/FastLED/src/fl/sketch_macros.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#if defined(__AVR__) || defined(ARDUINO_TEENSYLC) || defined(ARDUINO_TEENSY30) || defined(ARDUINO_TEENSY31)|| defined(STM32F1) || defined(ARDUINO_ARCH_RENESAS_UNO) || defined(ESP8266)
|
||||
#define SKETCH_HAS_LOTS_OF_MEMORY 0
|
||||
#else
|
||||
#define SKETCH_HAS_LOTS_OF_MEMORY 1
|
||||
#endif
|
||||
|
|
@ -1,16 +1,27 @@
|
|||
#include "fl/tile2x2.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/draw_visitor.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/raster.h"
|
||||
#include "fl/raster_sparse.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/warn.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
namespace fl {
|
||||
|
||||
namespace {
|
||||
static vec2i16 wrap(const vec2i16 &v, const vec2i16 &size) {
|
||||
// Wrap the vector v around the size
|
||||
return vec2i16(v.x % size.x, v.y % size.y);
|
||||
}
|
||||
|
||||
static vec2i16 wrap_x(const vec2i16 &v, const uint16_t width) {
|
||||
// Wrap the x component of the vector v around the size
|
||||
return vec2i16(v.x % width, v.y);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Tile2x2_u8::Rasterize(const Slice<const Tile2x2_u8> &tiles,
|
||||
XYRasterU8Sparse *out_raster) {
|
||||
out_raster->rasterize(tiles);
|
||||
|
|
@ -34,4 +45,66 @@ void Tile2x2_u8::scale(uint8_t scale) {
|
|||
}
|
||||
}
|
||||
|
||||
Tile2x2_u8_wrap::Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width) {
|
||||
const vec2i16 origin = from.origin();
|
||||
at(0, 0) = {wrap_x(vec2i16(origin.x, origin.y), width), from.at(0, 0)};
|
||||
at(0, 1) = {wrap_x(vec2i16(origin.x, origin.y + 1), width), from.at(0, 1)};
|
||||
at(1, 0) = {wrap_x(vec2i16(origin.x + 1, origin.y), width), from.at(1, 0)};
|
||||
at(1, 1) = {wrap_x(vec2i16(origin.x + 1, origin.y + 1), width),
|
||||
from.at(1, 1)};
|
||||
}
|
||||
|
||||
Tile2x2_u8_wrap::Data &Tile2x2_u8_wrap::at(uint16_t x, uint16_t y) {
|
||||
// Wrap around the edges
|
||||
x = (x + 2) % 2;
|
||||
y = (y + 2) % 2;
|
||||
return tile[y][x];
|
||||
}
|
||||
|
||||
const Tile2x2_u8_wrap::Data &Tile2x2_u8_wrap::at(uint16_t x, uint16_t y) const {
|
||||
// Wrap around the edges
|
||||
x = (x + 2) % 2;
|
||||
y = (y + 2) % 2;
|
||||
return tile[y][x];
|
||||
}
|
||||
|
||||
Tile2x2_u8_wrap::Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width,
|
||||
uint16_t height) {
|
||||
const vec2i16 origin = from.origin();
|
||||
at(0, 0) = {wrap(vec2i16(origin.x, origin.y), vec2i16(width, height)),
|
||||
from.at(0, 0)};
|
||||
at(0, 1) = {wrap(vec2i16(origin.x, origin.y + 1), vec2i16(width, height)),
|
||||
from.at(0, 1)};
|
||||
at(1, 0) = {wrap(vec2i16(origin.x + 1, origin.y), vec2i16(width, height)),
|
||||
from.at(1, 0)};
|
||||
at(1,
|
||||
1) = {wrap(vec2i16(origin.x + 1, origin.y + 1), vec2i16(width, height)),
|
||||
from.at(1, 1)};
|
||||
}
|
||||
|
||||
uint8_t Tile2x2_u8::maxValue() const {
|
||||
uint8_t max = 0;
|
||||
max = MAX(max, at(0, 0));
|
||||
max = MAX(max, at(0, 1));
|
||||
max = MAX(max, at(1, 0));
|
||||
max = MAX(max, at(1, 1));
|
||||
return max;
|
||||
}
|
||||
|
||||
Tile2x2_u8 Tile2x2_u8::MaxTile(const Tile2x2_u8 &a, const Tile2x2_u8 &b) {
|
||||
Tile2x2_u8 result;
|
||||
for (int x = 0; x < 2; ++x) {
|
||||
for (int y = 0; y < 2; ++y) {
|
||||
result.at(x, y) = MAX(a.at(x, y), b.at(x, y));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
rect<int16_t> Tile2x2_u8::bounds() const {
|
||||
vec2<int16_t> min = mOrigin;
|
||||
vec2<int16_t> max = mOrigin + vec2<int16_t>(2, 2);
|
||||
return rect<int16_t>(min, max);
|
||||
}
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "fl/geometry.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/pair.h"
|
||||
#include "fl/slice.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
|
|
@ -31,6 +32,8 @@ class Tile2x2_u8 {
|
|||
|
||||
void scale(uint8_t scale);
|
||||
|
||||
void setOrigin(int16_t x, int16_t y) { mOrigin = vec2<int16_t>(x, y); }
|
||||
|
||||
uint8_t &operator()(int x, int y) { return at(x, y); }
|
||||
uint8_t &at(int x, int y) { return mTile[y][x]; }
|
||||
const uint8_t &at(int x, int y) const { return mTile[y][x]; }
|
||||
|
|
@ -40,33 +43,19 @@ class Tile2x2_u8 {
|
|||
uint8_t &lower_right() { return at(1, 0); }
|
||||
uint8_t &upper_right() { return at(1, 1); }
|
||||
|
||||
uint8_t maxValue() const {
|
||||
uint8_t max = 0;
|
||||
max = MAX(max, at(0, 0));
|
||||
max = MAX(max, at(0, 1));
|
||||
max = MAX(max, at(1, 0));
|
||||
max = MAX(max, at(1, 1));
|
||||
return max;
|
||||
}
|
||||
const uint8_t &lower_left() const { return at(0, 0); }
|
||||
const uint8_t &upper_left() const { return at(0, 1); }
|
||||
const uint8_t &lower_right() const { return at(1, 0); }
|
||||
const uint8_t &upper_right() const { return at(1, 1); }
|
||||
|
||||
static Tile2x2_u8 Max(const Tile2x2_u8 &a, const Tile2x2_u8 &b) {
|
||||
Tile2x2_u8 result;
|
||||
for (int x = 0; x < 2; ++x) {
|
||||
for (int y = 0; y < 2; ++y) {
|
||||
result.at(x, y) = MAX(a.at(x, y), b.at(x, y));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
uint8_t maxValue() const;
|
||||
|
||||
static Tile2x2_u8 MaxTile(const Tile2x2_u8 &a, const Tile2x2_u8 &b);
|
||||
|
||||
vec2<int16_t> origin() const { return mOrigin; }
|
||||
|
||||
/// bounds => [begin_x, end_x) (where end_x is exclusive)
|
||||
rect<int16_t> bounds() const {
|
||||
vec2<int16_t> min = mOrigin;
|
||||
vec2<int16_t> max = mOrigin + vec2<int16_t>(2, 2);
|
||||
return rect<int16_t>(min, max);
|
||||
}
|
||||
rect<int16_t> bounds() const;
|
||||
|
||||
// Draws the subpixel tile to the led array.
|
||||
void draw(const CRGB &color, const XYMap &xymap, CRGB *out) const;
|
||||
|
|
@ -96,4 +85,24 @@ class Tile2x2_u8 {
|
|||
vec2<int16_t> mOrigin;
|
||||
};
|
||||
|
||||
class Tile2x2_u8_wrap {
|
||||
// This is a class that is like a Tile2x2_u8 but wraps around the edges.
|
||||
// This is useful for cylinder mapping where the x-coordinate wraps around
|
||||
// the width of the cylinder and the y-coordinate wraps around the height.
|
||||
// This converts a tile2x2 to a wrapped x,y version.
|
||||
public:
|
||||
using Data = fl::pair<vec2i16, uint8_t>; // absolute position, alpha
|
||||
|
||||
Tile2x2_u8_wrap() = default;
|
||||
Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width);
|
||||
Tile2x2_u8_wrap(const Tile2x2_u8 &from, uint16_t width, uint16_t height);
|
||||
|
||||
// Returns the absolute position and the alpha.
|
||||
Data &at(uint16_t x, uint16_t y);
|
||||
const Data &at(uint16_t x, uint16_t y) const;
|
||||
|
||||
private:
|
||||
Data tile[2][2] = {}; // zero filled.
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bilinear_expansion.h"
|
||||
#include "crgb.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
namespace fl {
|
||||
|
|
@ -17,9 +17,8 @@ uint8_t bilinearInterpolate(uint8_t v00, uint8_t v10, uint8_t v01, uint8_t v11,
|
|||
uint8_t bilinearInterpolatePowerOf2(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, uint8_t dx, uint8_t dy);
|
||||
|
||||
void bilinearExpandArbitrary(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
XYMap xyMap) {
|
||||
void upscaleArbitrary(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, XYMap xyMap) {
|
||||
uint16_t n = xyMap.getTotal();
|
||||
uint16_t outputWidth = xyMap.getWidth();
|
||||
uint16_t outputHeight = xyMap.getHeight();
|
||||
|
|
@ -82,8 +81,8 @@ uint8_t bilinearInterpolate(uint8_t v00, uint8_t v10, uint8_t v01, uint8_t v11,
|
|||
return result;
|
||||
}
|
||||
|
||||
void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, XYMap xyMap) {
|
||||
void upscalePowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, XYMap xyMap) {
|
||||
uint8_t width = xyMap.getWidth();
|
||||
uint8_t height = xyMap.getHeight();
|
||||
if (width != xyMap.getWidth() || height != xyMap.getHeight()) {
|
||||
|
|
@ -158,7 +157,7 @@ uint8_t bilinearInterpolatePowerOf2(uint8_t v00, uint8_t v10, uint8_t v01,
|
|||
}
|
||||
|
||||
// Floating-point version of bilinear interpolation
|
||||
uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t upscaleFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, float dx, float dy) {
|
||||
float dx_inv = 1.0f - dx;
|
||||
float dy_inv = 1.0f - dy;
|
||||
|
|
@ -179,9 +178,8 @@ uint8_t bilinearInterpolateFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
|||
}
|
||||
|
||||
// Floating-point version for arbitrary grid sizes
|
||||
void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
||||
uint16_t inputWidth, uint16_t inputHeight,
|
||||
XYMap xyMap) {
|
||||
void upscaleArbitraryFloat(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, XYMap xyMap) {
|
||||
uint16_t n = xyMap.getTotal();
|
||||
uint16_t outputWidth = xyMap.getWidth();
|
||||
uint16_t outputHeight = xyMap.getHeight();
|
||||
|
|
@ -214,11 +212,11 @@ void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
|||
|
||||
CRGB result;
|
||||
result.r =
|
||||
bilinearInterpolateFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
|
||||
upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
|
||||
result.g =
|
||||
bilinearInterpolateFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
|
||||
upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
|
||||
result.b =
|
||||
bilinearInterpolateFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
|
||||
upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
|
||||
|
||||
uint16_t idx = xyMap.mapToIndex(x, y);
|
||||
if (idx < n) {
|
||||
|
|
@ -229,8 +227,8 @@ void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
|
|||
}
|
||||
|
||||
// Floating-point version for power-of-two grid sizes
|
||||
void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, XYMap xyMap) {
|
||||
void upscaleFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, XYMap xyMap) {
|
||||
uint8_t outputWidth = xyMap.getWidth();
|
||||
uint8_t outputHeight = xyMap.getHeight();
|
||||
if (outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight()) {
|
||||
|
|
@ -267,11 +265,11 @@ void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
|||
|
||||
CRGB result;
|
||||
result.r =
|
||||
bilinearInterpolateFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
|
||||
upscaleFloat(c00.r, c10.r, c01.r, c11.r, dx, dy);
|
||||
result.g =
|
||||
bilinearInterpolateFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
|
||||
upscaleFloat(c00.g, c10.g, c01.g, c11.g, dx, dy);
|
||||
result.b =
|
||||
bilinearInterpolateFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
|
||||
upscaleFloat(c00.b, c10.b, c01.b, c11.b, dx, dy);
|
||||
|
||||
uint16_t idx = xyMap.mapToIndex(x, y);
|
||||
if (idx < n) {
|
||||
63
libraries/FastLED/src/fl/upscale.h
Normal file
63
libraries/FastLED/src/fl/upscale.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/// @file bilinear_expansion.h
|
||||
/// @brief Demonstrates how to mix noise generation with color palettes on a
|
||||
/// 2D LED matrix
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/xymap.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
/// @brief Performs bilinear interpolation for upscaling an image.
|
||||
/// @param output The output grid to write into the interpolated values.
|
||||
/// @param input The input grid to read from.
|
||||
/// @param inputWidth The width of the input grid.
|
||||
/// @param inputHeight The height of the input grid.
|
||||
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
|
||||
/// pixel is mapped outside of the range then it is clipped.
|
||||
void upscaleArbitrary(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap);
|
||||
|
||||
/// @brief Performs bilinear interpolation for upscaling an image.
|
||||
/// @param output The output grid to write into the interpolated values.
|
||||
/// @param input The input grid to read from.
|
||||
/// @param inputWidth The width of the input grid.
|
||||
/// @param inputHeight The height of the input grid.
|
||||
/// @param xyMap The XYMap to use to determine where to write the pixel. If the
|
||||
/// pixel is mapped outside of the range then it is clipped.
|
||||
void upscalePowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, fl::XYMap xyMap);
|
||||
|
||||
//
|
||||
inline void upscale(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap) {
|
||||
uint16_t outputWidth = xyMap.getWidth();
|
||||
uint16_t outputHeight = xyMap.getHeight();
|
||||
const bool wontFit =
|
||||
(outputWidth != xyMap.getWidth() || outputHeight != xyMap.getHeight());
|
||||
// if the input dimensions are not a power of 2 then we can't use the
|
||||
// optimized version.
|
||||
if (wontFit || (inputWidth & (inputWidth - 1)) ||
|
||||
(inputHeight & (inputHeight - 1))) {
|
||||
upscaleArbitrary(input, output, inputWidth, inputHeight, xyMap);
|
||||
} else {
|
||||
upscalePowerOf2(input, output, inputWidth, inputHeight, xyMap);
|
||||
}
|
||||
}
|
||||
|
||||
// These are here for testing purposes and are slow. Their primary use
|
||||
// is to test against the fixed integer version above.
|
||||
void upscaleFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
|
||||
uint8_t inputHeight, fl::XYMap xyMap);
|
||||
|
||||
void upscaleArbitraryFloat(const CRGB *input, CRGB *output, uint16_t inputWidth,
|
||||
uint16_t inputHeight, fl::XYMap xyMap);
|
||||
|
||||
uint8_t upscaleFloat(uint8_t v00, uint8_t v10, uint8_t v01,
|
||||
uint8_t v11, float dx, float dy);
|
||||
|
||||
} // namespace fl
|
||||
|
|
@ -237,7 +237,7 @@ XYPathPtr XYPath::NewCatmullRomPath(uint16_t width, uint16_t height,
|
|||
}
|
||||
|
||||
XYPathPtr XYPath::NewCustomPath(const fl::function<vec2f(float)> &f,
|
||||
const rect<int> &drawbounds,
|
||||
const rect<int16_t> &drawbounds,
|
||||
const TransformFloat &transform,
|
||||
const char *name) {
|
||||
|
||||
|
|
@ -250,7 +250,7 @@ XYPathPtr XYPath::NewCustomPath(const fl::function<vec2f(float)> &f,
|
|||
if (!transform.is_identity()) {
|
||||
out->setTransform(transform);
|
||||
}
|
||||
rect<int> bounds;
|
||||
rect<int16_t> bounds;
|
||||
if (path->hasDrawBounds(&bounds)) {
|
||||
if (!bounds.mMin.is_zero()) {
|
||||
// Set the bounds to the path's bounds
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@
|
|||
#include "fl/transform.h"
|
||||
#include "fl/xypath_impls.h"
|
||||
|
||||
#include "fl/avr_disallowed.h"
|
||||
|
||||
namespace fl {
|
||||
|
||||
class Gradient;
|
||||
|
|
@ -37,7 +35,6 @@ namespace xypath_detail {
|
|||
fl::Str unique_missing_name(const char *prefix = "XYCustomPath: ");
|
||||
} // namespace xypath_detail
|
||||
|
||||
AVR_DISALLOWED
|
||||
class XYPath : public Referent {
|
||||
public:
|
||||
/////////////////////////////////////////////
|
||||
|
|
@ -57,7 +54,7 @@ class XYPath : public Referent {
|
|||
// Custom path using just a function.
|
||||
static XYPathPtr
|
||||
NewCustomPath(const fl::function<vec2f(float)> &path,
|
||||
const rect<int> &drawbounds = rect<int>(),
|
||||
const rect<int16_t> &drawbounds = rect<int16_t>(),
|
||||
const TransformFloat &transform = TransformFloat(),
|
||||
const char *name = nullptr);
|
||||
|
||||
|
|
@ -148,10 +145,10 @@ class XYPathFunction : public XYPathGenerator {
|
|||
const Str name() const override { return mName; }
|
||||
void setName(const Str &name) { mName = name; }
|
||||
|
||||
fl::rect<int> drawBounds() const { return mDrawBounds; }
|
||||
void setDrawBounds(const fl::rect<int> &bounds) { mDrawBounds = bounds; }
|
||||
fl::rect<int16_t> drawBounds() const { return mDrawBounds; }
|
||||
void setDrawBounds(const fl::rect<int16_t> &bounds) { mDrawBounds = bounds; }
|
||||
|
||||
bool hasDrawBounds(fl::rect<int> *bounds) override {
|
||||
bool hasDrawBounds(fl::rect<int16_t> *bounds) override {
|
||||
if (bounds) {
|
||||
*bounds = mDrawBounds;
|
||||
}
|
||||
|
|
@ -161,7 +158,7 @@ class XYPathFunction : public XYPathGenerator {
|
|||
private:
|
||||
fl::function<vec2f(float)> mFunction;
|
||||
fl::Str mName = "XYPathFunction Unnamed";
|
||||
fl::rect<int> mDrawBounds;
|
||||
fl::rect<int16_t> mDrawBounds;
|
||||
};
|
||||
|
||||
} // namespace fl
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class XYPathGenerator : public Referent {
|
|||
virtual const Str name() const = 0;
|
||||
virtual vec2f compute(float alpha) = 0;
|
||||
// No writes when returning false.
|
||||
virtual bool hasDrawBounds(rect<int> *bounds) {
|
||||
virtual bool hasDrawBounds(rect<int16_t> *bounds) {
|
||||
FASTLED_UNUSED(bounds);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
#include "fl/scoped_ptr.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "fx/fx2d.h"
|
||||
#include "eorder.h"
|
||||
#include "pixel_controller.h" // For RGB_BYTE_0, RGB_BYTE_1, RGB_BYTE_2
|
||||
|
||||
#define ANIMARTRIX_INTERNAL
|
||||
#include "animartrix_detail.hpp"
|
||||
|
|
@ -93,6 +95,8 @@ class Animartrix : public Fx2d {
|
|||
int fxGet() const { return static_cast<int>(current_animation); }
|
||||
Str fxName() const override { return "Animartrix:"; }
|
||||
void fxNext(int fx = 1) { fxSet(fxGet() + fx); }
|
||||
void setColorOrder(EOrder order) { color_order = order; }
|
||||
EOrder getColorOrder() const { return color_order; }
|
||||
|
||||
private:
|
||||
friend void AnimartrixLoop(Animartrix &self, uint32_t now);
|
||||
|
|
@ -102,6 +106,7 @@ class Animartrix : public Fx2d {
|
|||
fl::scoped_ptr<FastLEDANIMartRIX> impl;
|
||||
CRGB *leds = nullptr; // Only set during draw, then unset back to nullptr.
|
||||
AnimartrixAnim current_animation = RGB_BLOBS5;
|
||||
EOrder color_order = RGB;
|
||||
};
|
||||
|
||||
void AnimartrixLoop(Animartrix &self, uint32_t now);
|
||||
|
|
@ -263,6 +268,17 @@ const char *Animartrix::getAnimationName(AnimartrixAnim animation) {
|
|||
void Animartrix::draw(DrawContext ctx) {
|
||||
this->leds = ctx.leds;
|
||||
AnimartrixLoop(*this, ctx.now);
|
||||
if (color_order != RGB) {
|
||||
for (int i = 0; i < mXyMap.getTotal(); ++i) {
|
||||
CRGB &pixel = ctx.leds[i];
|
||||
const uint8_t b0_index = RGB_BYTE0(color_order);
|
||||
const uint8_t b1_index = RGB_BYTE1(color_order);
|
||||
const uint8_t b2_index = RGB_BYTE2(color_order);
|
||||
pixel = CRGB(pixel.raw[b0_index], pixel.raw[b1_index],
|
||||
pixel.raw[b2_index]);
|
||||
}
|
||||
|
||||
}
|
||||
this->leds = nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#define FASTLED_INTERNAL
|
||||
#include "FastLED.h"
|
||||
#include "fl/bilinear_expansion.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/ptr.h"
|
||||
#include "fl/xymap.h"
|
||||
#include "fx/fx2d.h"
|
||||
|
|
@ -64,13 +64,13 @@ void ScaleUp::draw(DrawContext context) {
|
|||
void ScaleUp::expand(const CRGB *input, CRGB *output, uint16_t width,
|
||||
uint16_t height, XYMap mXyMap) {
|
||||
#if FASTLED_SCALE_UP == FASTLED_SCALE_UP_ALWAYS_POWER_OF_2
|
||||
bilinearExpandPowerOf2(input, output, width, height, mXyMap);
|
||||
fl::upscalePowerOf2(input, output, static_cast<uint8_t>(width), static_cast<uint8_t>(height), mXyMap);
|
||||
#elif FASTLED_SCALE_UP == FASTLED_SCALE_UP_HIGH_PRECISION
|
||||
bilinearExpandArbitrary(input, output, width, height, mXyMap);
|
||||
fl::upscaleArbitrary(input, output, width, height, mXyMap);
|
||||
#elif FASTLED_SCALE_UP == FASTLED_SCALE_UP_DECIDE_AT_RUNTIME
|
||||
bilinearExpand(input, output, width, height, mXyMap);
|
||||
fl::upscale(input, output, width, height, mXyMap);
|
||||
#elif FASTLED_SCALE_UP == FASTLED_SCALE_UP_FORCE_FLOATING_POINT
|
||||
bilinearExpandFloat(input, output, width, height, mXyMap);
|
||||
fl::upscaleFloat(input, output, static_cast<uint8_t>(width), static_cast<uint8_t>(height), mXyMap);
|
||||
#else
|
||||
#error "Invalid FASTLED_SCALE_UP"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "fl/bilinear_expansion.h"
|
||||
#include "fl/upscale.h"
|
||||
#include "fl/ptr.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/xymap.h"
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ typedef fl::FixedVector<int, 16> PinList16;
|
|||
|
||||
typedef uint8_t Pin;
|
||||
|
||||
bool gPsramInited = false;
|
||||
|
||||
|
||||
|
||||
// Maps multiple pins and CRGB strips to a single I2S_Esp32 object.
|
||||
|
|
@ -180,6 +182,13 @@ class Driver: public InternalI2SDriver {
|
|||
};
|
||||
|
||||
InternalI2SDriver* InternalI2SDriver::create() {
|
||||
if (!gPsramInited) {
|
||||
gPsramInited = true;
|
||||
bool ok = psramInit();
|
||||
if (!ok) {
|
||||
log_e("PSRAM initialization failed, I2S driver may crash.");
|
||||
}
|
||||
}
|
||||
return new Driver();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,20 @@
|
|||
#include "fl/vector.h"
|
||||
#include "eorder.h"
|
||||
|
||||
#ifndef FASTLED_INTERNAL
|
||||
// We need to do a check for the esp-idf version because very specific versions of the
|
||||
// esp-idf arduino core are broken.
|
||||
#include "platforms/esp/esp_version.h"
|
||||
// Broken in 3.0.2 (esp-idf 5.1.0)
|
||||
// Broken in 3.0.4 (esp-idf 5.1.0)
|
||||
// Broken in 3.0.7 (esp-idf 5.1.0)
|
||||
// Broken in 3.1.0 (esp-idf 5.3.2)
|
||||
#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 1, 0) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0)
|
||||
#error "I2S driver is known to not be compatible with ESP-IDF 5.1.0, upgrade to ESP-IDF 5.4.0 in Arduino core esp32 3.2.0+, see https://github.com/FastLED/FastLED/issues/1903"
|
||||
#elif ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 3, 2)
|
||||
#error "I2S driver is known to not be compatible with ESP-IDF 5.3.2, upgrade to ESP-IDF 5.4.0 in Arduino core esp32 3.2.0+, see https://github.com/FastLED/FastLED/issues/1903"
|
||||
#endif
|
||||
#endif // FASTLED_INTERNAL
|
||||
|
||||
namespace fl {
|
||||
|
||||
|
|
|
|||
|
|
@ -118,6 +118,11 @@ public:
|
|||
// GPIO 20-22, 24-26 used by default for SPI flash.
|
||||
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(24) | _FL_BIT(25) | _FL_BIT(26) | _FL_BIT(28) | _FL_BIT(29) | _FL_BIT(30))
|
||||
|
||||
#elif CONFIG_IDF_TARGET_ESP32P4
|
||||
// 55 GPIO pins. ESPIDF defines all pins as valid.
|
||||
// NOTE: GPIO 24 & 25 commonly used for USB and may cause flashes when uploading.
|
||||
#define FASTLED_UNUSABLE_PIN_MASK (0ULL | _FL_BIT(24) | _FL_BIT(25))
|
||||
|
||||
#elif CONFIG_IDF_TARGET_ESP32H2
|
||||
// 22 GPIO pins. ESPIDF defines all pins as valid.
|
||||
// ESP32-H2 datasheet not yet available, when it is, mask the pins commonly used by SPI flash.
|
||||
|
|
@ -131,6 +136,7 @@ public:
|
|||
#define FASTLED_UNUSABLE_PIN_MASK (0ULL)
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,11 +25,7 @@ private:
|
|||
|
||||
static RmtController5::DmaMode DefaultDmaMode()
|
||||
{
|
||||
#ifdef FASTLED_RMT_USE_DMA
|
||||
return RmtController5::DMA_ENABLED;
|
||||
#else
|
||||
return RmtController5::DMA_AUTO;
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#if !defined(PROGMEM)
|
||||
#define PROGMEM
|
||||
#define FL_PROGMEM
|
||||
#endif
|
||||
|
||||
#if !defined(FL_PROGMEM)
|
||||
#define FL_PROGMEM PROGMEM
|
||||
#endif
|
||||
|
||||
#define FL_PGM_READ_BYTE_NEAR(x) (*((const uint8_t *)(x)))
|
||||
#define FL_PGM_READ_WORD_NEAR(x) (*((const uint16_t *)(x)))
|
||||
#define FL_PGM_READ_DWORD_NEAR(x) (*((const uint32_t *)(x)))
|
||||
#define FL_ALIGN_PROGMEM
|
||||
#define FL_ALIGN_PROGMEM
|
||||
|
||||
#define FL_PROGMEM_USES_NULL 1
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
|
||||
FL_DISABLE_WARNING_PUSH
|
||||
FL_DISABLE_WARNING(global-constructors)
|
||||
FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
|
||||
|
||||
static const auto start_time = std::chrono::system_clock::now();
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class ActiveStripData : public fl::EngineEvents::Listener {
|
|||
updateScreenMap(id, screenmap);
|
||||
}
|
||||
|
||||
const bool hasScreenMap(int id) const { return mScreenMap.has(id); }
|
||||
bool hasScreenMap(int id) const { return mScreenMap.has(id); }
|
||||
|
||||
private:
|
||||
friend class fl::Singleton<ActiveStripData>;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class ActiveStripData2 : public fl::EngineEvents::Listener {
|
|||
updateScreenMap(id, screenmap);
|
||||
}
|
||||
|
||||
const bool hasScreenMap(int id) const { return mScreenMap.has(id); }
|
||||
bool hasScreenMap(int id) const { return mScreenMap.has(id); }
|
||||
|
||||
private:
|
||||
friend class fl::Singleton<ActiveStripData2>;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,19 @@
|
|||
#include "fl/hash_set.h"
|
||||
#include "fl/vector.h"
|
||||
|
||||
// Define an improved CHECK_CLOSE macro that provides better error messages
|
||||
#define CHECK_CLOSE(a, b, epsilon) \
|
||||
do { \
|
||||
float _a = (a); \
|
||||
float _b = (b); \
|
||||
float _diff = fabsf(_a - _b); \
|
||||
bool _result = _diff <= (epsilon); \
|
||||
if (!_result) { \
|
||||
printf("CHECK_CLOSE failed: |%f - %f| = %f > %f\n", (float)_a, \
|
||||
(float)_b, _diff, (float)(epsilon)); \
|
||||
} \
|
||||
CHECK(_result); \
|
||||
} while (0)
|
||||
|
||||
using namespace fl;
|
||||
|
||||
|
|
@ -80,9 +93,9 @@ namespace doctest {
|
|||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct StringMaker<fl::vector<T>> {
|
||||
static String convert(const fl::vector<T>& value) {
|
||||
template<typename T, typename Alloc>
|
||||
struct StringMaker<fl::vector<T, Alloc>> {
|
||||
static String convert(const fl::vector<T, Alloc>& value) {
|
||||
fl::Str out;
|
||||
out.append(value);
|
||||
return out.c_str();
|
||||
|
|
|
|||
|
|
@ -2,138 +2,119 @@
|
|||
|
||||
#include "fl/math_macros.h"
|
||||
#include "test.h"
|
||||
#include "fl/algorithm.h"
|
||||
|
||||
#include "fl/sstream.h"
|
||||
|
||||
#include "fl/corkscrew.h"
|
||||
|
||||
#include "fl/grid.h"
|
||||
#include "fl/tile2x2.h" // Ensure this header is included for Tile2x2_u8
|
||||
|
||||
#define NUM_LEDS 288
|
||||
|
||||
|
||||
#define TWO_PI (PI * 2.0)
|
||||
|
||||
// Define an improved CHECK_CLOSE macro that provides better error messages
|
||||
#define CHECK_CLOSE(a, b, epsilon) \
|
||||
do { \
|
||||
float _a = (a); \
|
||||
float _b = (b); \
|
||||
float _diff = fabsf(_a - _b); \
|
||||
bool _result = _diff <= (epsilon); \
|
||||
if (!_result) { \
|
||||
printf("CHECK_CLOSE failed: |%f - %f| = %f > %f\n", (float)_a, \
|
||||
(float)_b, _diff, (float)(epsilon)); \
|
||||
} \
|
||||
CHECK(_result); \
|
||||
} while (0)
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Corkscrew generateMap") {
|
||||
TEST_CASE("Corkscrew Circle10 test") {
|
||||
Corkscrew::Input input;
|
||||
input.totalHeight = 10.0f;
|
||||
input.totalAngle = TWO_PI;
|
||||
input.offsetCircumference = 0.0f;
|
||||
input.numLeds = 10;
|
||||
|
||||
Corkscrew::Output output = Corkscrew::generateMap(input);
|
||||
|
||||
CHECK_EQ(output.width, 10);
|
||||
CHECK_EQ(output.height, 1); // One vertical segment for one turn
|
||||
CHECK_EQ(output.mapping.size(), 10); // 10 LEDs around the corkscrew
|
||||
|
||||
CHECK_GE(output.mapping[0].x, 0.0f);
|
||||
CHECK_LE(output.mapping[0].x, 10.0f);
|
||||
CHECK_GE(output.mapping[0].y, 0.0f);
|
||||
CHECK_LE(output.mapping[0].y, 1.0f); // 1 vertical segment for 2π angle
|
||||
}
|
||||
|
||||
TEST_CASE("Corkscrew to Frame Buffer Mapping") {
|
||||
// Define the corkscrew input parameters
|
||||
|
||||
|
||||
|
||||
const int kCorkscrewTotalHeight = 1; // cm
|
||||
//const int CORKSCREW_WIDTH = 1; // Width of the corkscrew in pixels
|
||||
//const int CORKSCREW_HEIGHT = 1; // Height of the corkscrew in pixels
|
||||
const int kCorkscrewTurns = 2; // Default to 19 turns
|
||||
|
||||
|
||||
|
||||
Corkscrew::Input input;
|
||||
input.totalHeight = kCorkscrewTotalHeight;
|
||||
input.totalAngle = kCorkscrewTurns * 2 * PI; // Default to 19 turns
|
||||
input.offsetCircumference = 0.0f;
|
||||
input.numLeds = 3;
|
||||
|
||||
// Generate the corkscrew map
|
||||
|
||||
|
||||
|
||||
Corkscrew corkscrew(input);
|
||||
|
||||
volatile Corkscrew::Output* output = &corkscrew.access();
|
||||
|
||||
// vec2<int16_t> first = corkscrew.at(0);
|
||||
// vec2<int16_t> second = corkscrew.at(1);
|
||||
|
||||
Corkscrew::iterator it = corkscrew.begin();
|
||||
Corkscrew::iterator end = corkscrew.end();
|
||||
|
||||
fl::sstream ss;
|
||||
ss << "\n";
|
||||
ss << "width: " << output->width << "\n";
|
||||
ss << "height: " << output->height << "\n";
|
||||
|
||||
while (it != end) {
|
||||
ss << *it << "\n";
|
||||
++it;
|
||||
}
|
||||
|
||||
FASTLED_WARN(ss.str());
|
||||
|
||||
|
||||
MESSAGE("done");
|
||||
input.totalLength = 10.0f; // Total length of the corkscrew in centimeters
|
||||
input.totalHeight = 0.0f;
|
||||
input.totalTurns = 1.0f;
|
||||
input.offsetCircumference = 0.0f; // No offset
|
||||
input.numLeds = 10; // Default to dense 144 LEDs times two strips
|
||||
Corkscrew::State output = Corkscrew::generateState(input);
|
||||
fl::vector<vec2f> expected_values;
|
||||
expected_values.push_back(vec2f(0.0f, 0.0f)); // First LED at the bottom
|
||||
expected_values.push_back(vec2f(1.0f, 0.0f)); // Second LED in the middle
|
||||
expected_values.push_back(vec2f(2.0f, 0.0f)); // Third LED at the top
|
||||
expected_values.push_back(vec2f(3.0f, 0.0f)); // Fourth LED at the top
|
||||
expected_values.push_back(vec2f(4.0f, 0.0f)); // Fifth LED at the top
|
||||
expected_values.push_back(vec2f(5.0f, 0.0f)); // Sixth LED at the top
|
||||
expected_values.push_back(vec2f(6.0f, 0.0f)); // Seventh LED at the top
|
||||
expected_values.push_back(vec2f(7.0f, 0.0f)); // Eighth LED at the top
|
||||
expected_values.push_back(vec2f(8.0f, 0.0f)); // Ninth LED at the top
|
||||
expected_values.push_back(vec2f(9.0f, 0.0f)); // Tenth LED at the top
|
||||
|
||||
REQUIRE_EQ(output.width, 10);
|
||||
REQUIRE_EQ(output.height, 1);
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Corkscrew generateMap with two turns") {
|
||||
Corkscrew::Input input;
|
||||
input.totalHeight = 10.0f;
|
||||
input.totalAngle = 2 * TWO_PI; // Two full turns
|
||||
input.numLeds = 10; // 10 LEDs around the corkscrew
|
||||
input.offsetCircumference = 0.0f;
|
||||
TEST_CASE("Tile2x2_u8_wrap wrap-around test with width and height") {
|
||||
// Initialize a Tile2x2_u8 with known values and set origin beyond boundaries
|
||||
Tile2x2_u8 originalTile;
|
||||
originalTile.setOrigin(3, 3); // Set the origin beyond the width and height
|
||||
originalTile.at(0, 0) = 1;
|
||||
originalTile.at(0, 1) = 2;
|
||||
originalTile.at(1, 0) = 3;
|
||||
originalTile.at(1, 1) = 4;
|
||||
|
||||
Corkscrew::Output output = Corkscrew::generateMap(input);
|
||||
// Convert to Tile2x2_u8_wrap with given width and height
|
||||
uint16_t width = 2;
|
||||
uint16_t height = 2;
|
||||
Tile2x2_u8_wrap cycTile(originalTile, width, height);
|
||||
|
||||
CHECK_EQ(output.width, 5);
|
||||
CHECK_EQ(output.height, 2); // Two vertical segments for two turns
|
||||
CHECK_EQ(output.mapping.size(), 10); // 5 width * 2 height
|
||||
// Verify that the conversion wraps around correctly
|
||||
REQUIRE_EQ(cycTile.at(0, 0).first.x, 1); // Wraps around to (1, 1)
|
||||
REQUIRE_EQ(cycTile.at(0, 0).first.y, 1);
|
||||
REQUIRE_EQ(cycTile.at(0, 1).first.x, 1); // Wraps around to (1, 0)
|
||||
REQUIRE_EQ(cycTile.at(0, 1).first.y, 0);
|
||||
REQUIRE_EQ(cycTile.at(1, 0).first.x, 0); // Wraps around to (0, 1)
|
||||
REQUIRE_EQ(cycTile.at(1, 0).first.y, 1);
|
||||
REQUIRE_EQ(cycTile.at(1, 1).first.x, 0); // Wraps around to (0, 0)
|
||||
REQUIRE_EQ(cycTile.at(1, 1).first.y, 0);
|
||||
|
||||
// Check first pixel for correctness (basic integrity)
|
||||
CHECK_GE(output.mapping[0].x, 0.0f);
|
||||
CHECK_LE(output.mapping[0].x, 5.0f);
|
||||
CHECK_GE(output.mapping[0].y, 0.0f);
|
||||
CHECK_LE(output.mapping[0].y, 2.0f); // 2 vertical segments for 4π angle
|
||||
// Verify that the values are correct
|
||||
REQUIRE_EQ(cycTile.at(0, 0).second, 1);
|
||||
REQUIRE_EQ(cycTile.at(0, 1).second, 2);
|
||||
REQUIRE_EQ(cycTile.at(1, 0).second, 3);
|
||||
REQUIRE_EQ(cycTile.at(1, 1).second, 4);
|
||||
}
|
||||
|
||||
TEST_CASE("Corkscrew circumference test") {
|
||||
Corkscrew::Input input;
|
||||
// Use defaults: totalHeight = 100, totalAngle = 19 * 2 * PI
|
||||
input.totalHeight = 23.25f; // Total height of the corkscrew in centimeters
|
||||
input.totalAngle = 19.0f * TWO_PI; // Default to 19 turns
|
||||
input.offsetCircumference = 0.0f; // No offset
|
||||
input.numLeds = 288; // Default to dense 144 LEDs times two strips
|
||||
TEST_CASE("Tile2x2_u8_wrap conversion with width and height") {
|
||||
// Initialize a Tile2x2_u8 with known values
|
||||
Tile2x2_u8 originalTile;
|
||||
originalTile.setOrigin(0, 0); // Set the origin to (0, 0)
|
||||
originalTile.at(0, 0) = 1;
|
||||
originalTile.at(0, 1) = 2;
|
||||
originalTile.at(1, 0) = 3;
|
||||
originalTile.at(1, 1) = 4;
|
||||
|
||||
Corkscrew::Output output = Corkscrew::generateMap(input);
|
||||
// Convert to Tile2x2_u8_wrap with given width and height
|
||||
uint16_t width = 2;
|
||||
uint16_t height = 2;
|
||||
Tile2x2_u8_wrap cycTile(originalTile, width, height);
|
||||
|
||||
// Basic sanity checks
|
||||
CHECK_EQ(output.width, 16);
|
||||
CHECK_EQ(output.height, 19);
|
||||
CHECK_EQ(output.mapping.size(), 288);
|
||||
|
||||
// Check that circumference matches calculated value
|
||||
// float expectedCircumference = 100.0f / 19.0f;
|
||||
// CHECK_CLOSE(output.circumference, expectedCircumference, 0.01f);
|
||||
// Verify that the conversion is correct
|
||||
REQUIRE_EQ(cycTile.at(0, 0).second, 1);
|
||||
REQUIRE_EQ(cycTile.at(0, 1).second, 2);
|
||||
REQUIRE_EQ(cycTile.at(1, 0).second, 3);
|
||||
REQUIRE_EQ(cycTile.at(1, 1).second, 4);
|
||||
}
|
||||
|
||||
TEST_CASE("Tile2x2_u8_wrap conversion test") {
|
||||
// Initialize a Tile2x2_u8 with known values and a specific origin
|
||||
Tile2x2_u8 originalTile;
|
||||
originalTile.setOrigin(50, 50); // Set the origin to (50, 50)
|
||||
originalTile.at(0, 0) = 1; // Initialize the missing element
|
||||
originalTile.at(0, 1) = 2;
|
||||
originalTile.at(1, 0) = 3;
|
||||
originalTile.at(1, 1) = 4;
|
||||
|
||||
// Convert to Tile2x2_u8_wrap with a given width
|
||||
uint16_t width = 10;
|
||||
Tile2x2_u8_wrap cycTile(originalTile, width);
|
||||
|
||||
// Verify that the conversion is correct
|
||||
REQUIRE_EQ(cycTile.at(0, 0).second, 1);
|
||||
REQUIRE_EQ(cycTile.at(0, 1).second, 2);
|
||||
REQUIRE_EQ(cycTile.at(1, 0).second, 3);
|
||||
REQUIRE_EQ(cycTile.at(1, 1).second, 4);
|
||||
|
||||
// Verify wrap-around behavior on the x-axis
|
||||
REQUIRE_EQ(cycTile.at(2, 2).second, 1); // Wraps around to (0, 0)
|
||||
REQUIRE_EQ(cycTile.at(2, 3).second, 2); // Wraps around to (0, 1)
|
||||
REQUIRE_EQ(cycTile.at(3, 2).second, 3); // Wraps around to (1, 0)
|
||||
REQUIRE_EQ(cycTile.at(3, 3).second, 4); // Wraps around to (1, 1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"m5stack/M5Utility": "*",
|
||||
"m5stack/M5HAL": "*"
|
||||
},
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name=M5UnitUnified
|
||||
version=0.1.4
|
||||
version=0.1.5
|
||||
author=M5Stack
|
||||
maintainer=M5Stack
|
||||
sentence=M5UnitUnified is a library for unified handling of various M5 units products. (Alpha version)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public:
|
|||
|
||||
/*!
|
||||
@class ComponentTestBase
|
||||
@brief UnitComponent Derived class for testing
|
||||
@brief UnitComponent Derived class for testing (I2C)
|
||||
@tparam U m5::unit::Component-derived classes to be tested
|
||||
@tparam TP parameter type for testing. see also INSTANTIATE_TEST_SUITE_P
|
||||
*/
|
||||
|
|
@ -109,6 +109,67 @@ protected:
|
|||
m5::unit::UnitUnified Units;
|
||||
};
|
||||
|
||||
/*!
|
||||
@class GPIOComponentTestBase
|
||||
@brief UnitComponent Derived class for testing (GPIO)
|
||||
@tparam U m5::unit::Component-derived classes to be tested
|
||||
@tparam TP parameter type for testing. see also INSTANTIATE_TEST_SUITE_P
|
||||
*/
|
||||
template <typename U, typename TP>
|
||||
class GPIOComponentTestBase : public ::testing::TestWithParam<TP> {
|
||||
static_assert(std::is_base_of<m5::unit::Component, U>::value, "U must be derived from Component");
|
||||
|
||||
protected:
|
||||
virtual void SetUp() override
|
||||
{
|
||||
unit.reset(get_instance());
|
||||
if (!unit) {
|
||||
FAIL() << "Failed to get_instance";
|
||||
GTEST_SKIP();
|
||||
return;
|
||||
}
|
||||
ustr = m5::utility::formatString("%s:%s", unit->deviceName(), is_using_hal() ? "HAL" : "GPIO");
|
||||
if (!begin()) {
|
||||
FAIL() << "Failed to begin " << ustr;
|
||||
GTEST_SKIP();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void TearDown() override
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool begin()
|
||||
{
|
||||
auto pin_num_gpio_in = M5.getPin(m5::pin_name_t::port_b_in);
|
||||
auto pin_num_gpio_out = M5.getPin(m5::pin_name_t::port_b_out);
|
||||
if (pin_num_gpio_in < 0 || pin_num_gpio_out < 0) {
|
||||
M5_LOGW("PortB is not available");
|
||||
Wire.end();
|
||||
pin_num_gpio_in = M5.getPin(m5::pin_name_t::port_a_pin1);
|
||||
pin_num_gpio_out = M5.getPin(m5::pin_name_t::port_a_pin2);
|
||||
}
|
||||
M5_LOGI("getPin: %d,%d", pin_num_gpio_in, pin_num_gpio_out);
|
||||
|
||||
if (is_using_hal()) {
|
||||
// Using M5HAL
|
||||
// TODO Not yet
|
||||
return false;
|
||||
}
|
||||
// Using TwoWire
|
||||
return Units.add(*unit, pin_num_gpio_in, pin_num_gpio_out) && Units.begin();
|
||||
}
|
||||
|
||||
//!@brief Function returning true if M5HAL is used (decision based on TP)
|
||||
virtual bool is_using_hal() const = 0;
|
||||
//! @brief return m5::unit::Component-derived class instance (decision based on TP)
|
||||
virtual U* get_instance() = 0;
|
||||
|
||||
std::string ustr{};
|
||||
std::unique_ptr<U> unit{};
|
||||
m5::unit::UnitUnified Units;
|
||||
};
|
||||
|
||||
} // namespace googletest
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@ m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::read_analog(uint16_t& value,
|
|||
}
|
||||
|
||||
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::pulse_in(uint32_t& duration, const gpio_num_t pin, const int state,
|
||||
const uint32_t timeout_us = 30000)
|
||||
const uint32_t timeout_us)
|
||||
{
|
||||
duration = 0;
|
||||
auto start = esp_timer_get_time();
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public:
|
|||
}
|
||||
|
||||
inline virtual m5::hal::error::error_t pulseInRX(uint32_t& duration, const int state,
|
||||
const uint32_t timeout_us) override
|
||||
const uint32_t timeout_us = 30000) override
|
||||
{
|
||||
return pulse_in(duration, rx_pin(), state, timeout_us);
|
||||
}
|
||||
|
|
@ -114,7 +114,7 @@ public:
|
|||
return read_analog(v, tx_pin());
|
||||
}
|
||||
inline virtual m5::hal::error::error_t pulseInTX(uint32_t& duration, const int state,
|
||||
const uint32_t timeout_us) override
|
||||
const uint32_t timeout_us = 30000) override
|
||||
{
|
||||
return pulse_in(duration, tx_pin(), state, timeout_us);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ bool declrare_use_rmt_channel(const int ch)
|
|||
return false;
|
||||
}
|
||||
|
||||
void clear_use_rmt_channel(const int ch)
|
||||
{
|
||||
if (ch >= 0 && ch < RMT_CHANNEL_MAX) {
|
||||
using_rmt_channel_bits &= ~(1U << ch);
|
||||
}
|
||||
}
|
||||
|
||||
rmt_config_t to_rmt_config_tx(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
|
||||
{
|
||||
rmt_config_t out{};
|
||||
|
|
@ -78,6 +85,16 @@ public:
|
|||
GPIOImplV1(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase::GPIOImpl(rx_pin, tx_pin)
|
||||
{
|
||||
}
|
||||
virtual ~GPIOImplV1()
|
||||
{
|
||||
rmt_tx_stop(_tx_config.channel);
|
||||
rmt_driver_uninstall(_tx_config.channel);
|
||||
clear_use_rmt_channel(_tx_config.channel);
|
||||
|
||||
rmt_rx_stop(_rx_config.channel);
|
||||
rmt_driver_uninstall(_rx_config.channel);
|
||||
clear_use_rmt_channel(_rx_config.channel);
|
||||
}
|
||||
|
||||
virtual bool begin(const gpio::adapter_config_t& cfg) override
|
||||
{
|
||||
|
|
@ -137,8 +154,8 @@ public:
|
|||
}
|
||||
return err == ESP_OK ? m5::hal::error::error_t::OK : m5::hal::error::error_t::UNKNOWN_ERROR;
|
||||
}
|
||||
M5_LIB_LOGE("Failed invalid config");
|
||||
|
||||
M5_LIB_LOGE("Failed invalid config");
|
||||
return m5::hal::error::error_t::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,17 @@ public:
|
|||
GPIOImplV2(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase::GPIOImpl(rx_pin, tx_pin)
|
||||
{
|
||||
}
|
||||
virtual ~GPIOImplV2()
|
||||
{
|
||||
if (_tx_handle) {
|
||||
rmt_disable(_tx_handle);
|
||||
rmt_del_channel(_tx_handle);
|
||||
}
|
||||
if (_rx_handle) {
|
||||
rmt_disable(_rx_handle);
|
||||
rmt_del_channel(_rx_handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool begin(const gpio::adapter_config_t& cfg)
|
||||
{
|
||||
|
|
@ -88,6 +99,9 @@ public:
|
|||
rmt_transmit_config_t tx_config = {};
|
||||
auto err = rmt_transmit(_tx_handle, copy_encoder, (gpio::m5_rmt_item_t*)data, len * sizeof(gpio::m5_rmt_item_t),
|
||||
&tx_config);
|
||||
if (err != ESP_OK) {
|
||||
M5_LIB_LOGE("Failed to transmit %d:%s", err, esp_err_to_name(err));
|
||||
}
|
||||
if (err == ESP_OK && waitMs) {
|
||||
err = rmt_tx_wait_all_done(_tx_handle, waitMs);
|
||||
if (err != ESP_OK) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"url": "https://github.com/m5stack/M5Utility.git"
|
||||
},
|
||||
"dependencies": [],
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name=M5Utility
|
||||
version=0.0.2
|
||||
version=0.0.3
|
||||
author=M5Stack
|
||||
maintainer=M5Stack
|
||||
sentence=Library for other M5 libraries and products
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ extends = m5base
|
|||
board = m5stack-nanoc6
|
||||
platform = https://github.com/platformio/platform-espressif32.git
|
||||
platform_packages =
|
||||
platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git
|
||||
platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.7
|
||||
platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1
|
||||
board_build.partitions = default.csv
|
||||
lib_deps = ${env.lib_deps}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "m5_utility/string.hpp"
|
||||
#include "m5_utility/conversion.hpp"
|
||||
#include "m5_utility/math.hpp"
|
||||
#include "m5_utility/button_status.hpp"
|
||||
#include "m5_utility/misc.hpp"
|
||||
|
||||
/*!
|
||||
|
|
|
|||
84
libraries/M5Utility/src/m5_utility/button_status.cpp
Normal file
84
libraries/M5Utility/src/m5_utility/button_status.cpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file button_status.cpp
|
||||
@brief Button status management
|
||||
*/
|
||||
|
||||
#include "button_status.hpp"
|
||||
|
||||
namespace m5 {
|
||||
namespace utility {
|
||||
namespace button {
|
||||
|
||||
void Status::setState(const uint32_t msec, const button_state_t state)
|
||||
{
|
||||
if (_currentState == button_state_t::state_decide_click_count) {
|
||||
_clickCount = 0;
|
||||
}
|
||||
|
||||
_lastMsec = msec;
|
||||
bool flg_timeout = (msec - _lastClicked > _msecHold);
|
||||
auto new_state = state;
|
||||
switch (state) {
|
||||
case button_state_t::state_nochange:
|
||||
if (flg_timeout && !_press && _clickCount) {
|
||||
if (_oldPress == 0 && _currentState == button_state_t::state_nochange) {
|
||||
new_state = button_state_t::state_decide_click_count;
|
||||
} else {
|
||||
_clickCount = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case button_state_t::state_clicked:
|
||||
++_clickCount;
|
||||
_lastClicked = msec;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_currentState = new_state;
|
||||
}
|
||||
|
||||
void Status::setRawState(const uint32_t msec, const bool press)
|
||||
{
|
||||
button_state_t state = button_state_t::state_nochange;
|
||||
bool disable_db = (msec - _lastMsec) > _msecDebounce;
|
||||
auto oldPress = _press;
|
||||
_oldPress = oldPress;
|
||||
if (_raw_press != press) {
|
||||
_raw_press = press;
|
||||
_lastRawChange = msec;
|
||||
}
|
||||
if (disable_db || msec - _lastRawChange >= _msecDebounce) {
|
||||
if (press != (0 != oldPress)) {
|
||||
_lastChange = msec;
|
||||
}
|
||||
|
||||
if (press) {
|
||||
uint32_t holdPeriod = msec - _lastChange;
|
||||
_lastHoldPeriod = holdPeriod;
|
||||
if (!oldPress) {
|
||||
_press = 1;
|
||||
} else if (oldPress == 1 && (holdPeriod >= _msecHold)) {
|
||||
_press = 2;
|
||||
state = button_state_t::state_hold;
|
||||
}
|
||||
} else {
|
||||
_press = 0;
|
||||
if (oldPress == 1) {
|
||||
state = button_state_t::state_clicked;
|
||||
}
|
||||
}
|
||||
}
|
||||
setState(msec, state);
|
||||
}
|
||||
|
||||
} // namespace button
|
||||
} // namespace utility
|
||||
} // namespace m5
|
||||
182
libraries/M5Utility/src/m5_utility/button_status.hpp
Normal file
182
libraries/M5Utility/src/m5_utility/button_status.hpp
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file button_status.hpp
|
||||
@brief Button status management
|
||||
*/
|
||||
#ifndef M5_UTILITY_BUTTON_STATUS_HPP
|
||||
#define M5_UTILITY_BUTTON_STATUS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace m5 {
|
||||
namespace utility {
|
||||
namespace button {
|
||||
|
||||
/*!
|
||||
@class m5::utility::button::Status
|
||||
@brief Button status management
|
||||
@note Class compatible with Button_Class in M5Unified
|
||||
*/
|
||||
class Status {
|
||||
public:
|
||||
/*!
|
||||
@enum button_state_t
|
||||
@brief Button status
|
||||
*/
|
||||
enum class button_state_t : uint8_t { state_nochange, state_clicked, state_hold, state_decide_click_count };
|
||||
|
||||
/*!
|
||||
@brief Constructor
|
||||
@param hold_ms Time to be considered hold(ms)
|
||||
@param debounce_ms Debounce time(ms)
|
||||
*/
|
||||
Status(const uint16_t hold_ms = 500, const uint16_t debounce_ms = 10)
|
||||
: _msecHold{hold_ms}, _msecDebounce{debounce_ms}
|
||||
{
|
||||
}
|
||||
|
||||
///@name Settings
|
||||
///@{
|
||||
//! @brief Set debounce time(ms)
|
||||
inline void setDebounceThreshold(const uint32_t msec)
|
||||
{
|
||||
_msecDebounce = msec;
|
||||
}
|
||||
//! @brief Set time to be considered hold(ms)
|
||||
inline void setHoldThreshold(const uint32_t msec)
|
||||
{
|
||||
_msecHold = msec;
|
||||
}
|
||||
//! @brief Gets the debounce time(ms)
|
||||
inline uint32_t getDebounceThreshold(void) const
|
||||
{
|
||||
return _msecDebounce;
|
||||
}
|
||||
//! @brief Gets the time to be considered hold(ms)
|
||||
inline uint32_t getHoldThreshold(void) const
|
||||
{
|
||||
return _msecHold;
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Button status
|
||||
///@{
|
||||
//! @brief Is pressed?
|
||||
bool isPressed(void) const
|
||||
{
|
||||
return _press;
|
||||
}
|
||||
//! @brief Is released?
|
||||
bool isReleased(void) const
|
||||
{
|
||||
return !_press;
|
||||
}
|
||||
//! @brief Returns true if the button is currently held pressed
|
||||
bool isHolding(void) const
|
||||
{
|
||||
return _press == 2;
|
||||
}
|
||||
//! @brief Returns true if button was pressed
|
||||
bool wasPressed(void) const
|
||||
{
|
||||
return !_oldPress && _press;
|
||||
}
|
||||
//! @brief Returns true if button was released
|
||||
bool wasReleased(void) const
|
||||
{
|
||||
return _oldPress && !_press;
|
||||
}
|
||||
//! @brief Returns true when the button is pressed briefly and released
|
||||
bool wasClicked(void) const
|
||||
{
|
||||
return _currentState == button_state_t::state_clicked;
|
||||
}
|
||||
//! @brief Returns true when the button has been held pressed for a while
|
||||
bool wasHold(void) const
|
||||
{
|
||||
return _currentState == button_state_t::state_hold;
|
||||
}
|
||||
//! @brief Returns true when some time has passed since the button was single clicked
|
||||
bool wasSingleClicked(void) const
|
||||
{
|
||||
return _currentState == button_state_t::state_decide_click_count && _clickCount == 1;
|
||||
}
|
||||
//! @brief Returns true when some time has passed since the button was double clicked
|
||||
bool wasDoubleClicked(void) const
|
||||
{
|
||||
return _currentState == button_state_t::state_decide_click_count && _clickCount == 2;
|
||||
}
|
||||
//! @brief Returns true when some time has passed since the button was multiple clicked
|
||||
bool wasDecideClickCount(void) const
|
||||
{
|
||||
return _currentState == button_state_t::state_decide_click_count;
|
||||
}
|
||||
//! @brief Gets the number of consecutive button clicks
|
||||
uint8_t getClickCount(void) const
|
||||
{
|
||||
return _clickCount;
|
||||
}
|
||||
//! @brief Has the button press state changed?
|
||||
bool wasChangePressed(void) const
|
||||
{
|
||||
return ((bool)_press) != ((bool)_oldPress);
|
||||
}
|
||||
//! @brief Pressed and released a button for more than the set hold time?
|
||||
bool wasReleasedAfterHold(void) const
|
||||
{
|
||||
return !_press && _oldPress == 2;
|
||||
}
|
||||
//! @brief Was it pressed for more than the specified time?
|
||||
bool wasReleaseFor(const uint32_t ms) const
|
||||
{
|
||||
return _oldPress && !_press && _lastHoldPeriod >= ms;
|
||||
}
|
||||
//! @brief Is pressed for more than the specified time?
|
||||
bool pressedFor(const uint32_t ms) const
|
||||
{
|
||||
return (_press && _lastMsec - _lastChange >= ms);
|
||||
}
|
||||
//! @brief Is released for more than the specified time?
|
||||
bool releasedFor(const uint32_t ms) const
|
||||
{
|
||||
return (!_press && _lastMsec - _lastChange >= ms);
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Status
|
||||
///@{
|
||||
void setRawState(const uint32_t msec, const bool press);
|
||||
void setState(const uint32_t msec, const button_state_t state);
|
||||
inline button_state_t getState(void) const
|
||||
{
|
||||
return _currentState;
|
||||
}
|
||||
inline uint32_t lastChange(void) const
|
||||
{
|
||||
return _lastChange;
|
||||
}
|
||||
inline uint32_t getUpdateMsec(void) const
|
||||
{
|
||||
return _lastMsec;
|
||||
}
|
||||
///@}
|
||||
|
||||
private:
|
||||
uint16_t _msecHold{500}, _msecDebounce{10};
|
||||
uint32_t _lastMsec{}, _lastChange{}, _lastRawChange{}, _lastClicked{};
|
||||
uint16_t _lastHoldPeriod{};
|
||||
button_state_t _currentState{button_state_t::state_nochange};
|
||||
bool _raw_press{};
|
||||
// 0:release 1:click 2:holding
|
||||
uint8_t _press{}, _oldPress{}, _clickCount{};
|
||||
};
|
||||
|
||||
} // namespace button
|
||||
} // namespace utility
|
||||
} // namespace m5
|
||||
|
||||
#endif
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Spdx-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue