updated libraries

This commit is contained in:
stuce-bot 2025-06-30 21:41:49 +02:00
parent d5d5d000b3
commit 3b7e68065a
102 changed files with 3020 additions and 1624 deletions

View file

@ -8,6 +8,7 @@ FastLED Library
[![Documentation](https://img.shields.io/badge/Docs-Doxygen-blue.svg)](http://fastled.io/docs) [![Documentation](https://img.shields.io/badge/Docs-Doxygen-blue.svg)](http://fastled.io/docs)
[![Reddit](https://img.shields.io/badge/reddit-/r/FastLED-orange.svg?logo=reddit)](https://www.reddit.com/r/FastLED/) [![Reddit](https://img.shields.io/badge/reddit-/r/FastLED-orange.svg?logo=reddit)](https://www.reddit.com/r/FastLED/)
Want to control a strip of leds? Or control 10's of thousands? FastLED has your back. 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/). 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: 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! #### Major release for tech-artists!
@ -248,6 +253,10 @@ Update: max overclock has been reported at +70%: https://www.reddit.com/r/FastLE
[![esp32h2](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml) [![esp32h2](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32h2.yml)
[![esp32p4](https://github.com/FastLED/FastLED/actions/workflows/build_esp32p4.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32p4.yml)
*Specific features* *Specific features*
[![esp32_i2s_ws2812](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml) [![esp32_i2s_ws2812](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml/badge.svg)](https://github.com/FastLED/FastLED/actions/workflows/build_esp32_i2s_ws2812.yml)

View file

@ -24,10 +24,10 @@ Release notes should list highlight changes (not necessarily all minor bug fixes
Git commands to commit and tag release' Git commands to commit and tag release'
```bash ```bash
$ git commit -am "Rev 3.9.20 - Misc fixes" $ git commit -am "Rev 3.10.1 - Bug fix for 3.10.0"
$ git tag 3.9.20 master $ git tag 3.10.1 master
$ git push $ git push
$ git push origin 3.9.20 $ git push origin 3.10.1
``` ```
Then use the GitHub UI to make a new “Release”: Then use the GitHub UI to make a new “Release”:

View file

@ -16,7 +16,7 @@ from ci.locked_print import locked_print
HERE = Path(__file__).parent.resolve() HERE = Path(__file__).parent.resolve()
LIBS = ["src", "ci"] LIBS = ["src"]
EXTRA_LIBS = [ EXTRA_LIBS = [
"https://github.com/me-no-dev/ESPAsyncWebServer.git", "https://github.com/me-no-dev/ESPAsyncWebServer.git",
"ArduinoOTA", "ArduinoOTA",
@ -45,6 +45,7 @@ DEFAULT_BOARDS_NAMES = [
"ATtiny1616", "ATtiny1616",
"esp32c6", "esp32c6",
"esp32s3", "esp32s3",
"esp32p4",
"yun", "yun",
"digix", "digix",
"teensy30", "teensy30",
@ -69,16 +70,22 @@ OTHER_BOARDS_NAMES = [
# Examples to compile. # Examples to compile.
DEFAULT_EXAMPLES = [ DEFAULT_EXAMPLES = [
"Animartrix",
"Apa102", "Apa102",
"Apa102HD", "Apa102HD",
"Apa102HDOverride", "Apa102HDOverride",
"Audio",
"Blink", "Blink",
"Blur", "Blur",
"Chromancer", "Chromancer",
"ColorPalette", "ColorPalette",
"ColorTemperature", "ColorTemperature",
"Corkscrew",
"CompileTest",
"Cylon", "Cylon",
"DemoReel100", "DemoReel100",
"Downscale",
"FestivalStick",
"FirstLight", "FirstLight",
"Fire2012", "Fire2012",
"Multiple/MultipleStripsInOneArray", "Multiple/MultipleStripsInOneArray",
@ -96,6 +103,8 @@ DEFAULT_EXAMPLES = [
"RGBWEmulated", "RGBWEmulated",
"TwinkleFox", "TwinkleFox",
"XYMatrix", "XYMatrix",
"FireMatrix",
"FireCylinder",
"FxGfx2Video", "FxGfx2Video",
"FxSdCard", "FxSdCard",
"FxCylon", "FxCylon",

View file

@ -11,6 +11,7 @@ ESP32_IDF_5_1_PIOARDUINO = "https://github.com/pioarduino/platform-espressif32/r
# TODO: Upgrade toolkit to 5.3 # 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_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 = ( ESP32_IDF_5_1_PIOARDUINO_LATEST = (
"https://github.com/pioarduino/platform-espressif32.git#develop" "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 # Old fork that we were using
# ESP32_IDF_5_1_PIOARDUINO = "https://github.com/zackees/platform-espressif32#Arduino/IDF5" # 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"] = [] ALL: list["Board"] = []
@ -156,7 +159,7 @@ ESP32_C2_DEVKITM_1 = Board(
board_name="esp32c2", board_name="esp32c2",
real_board_name="esp32-c2-devkitm-1", real_board_name="esp32-c2-devkitm-1",
use_pio_run=True, 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"], defines=["CONFIG_IDF_TARGET_ESP32C2=1"],
) )
@ -175,14 +178,7 @@ ESP32_C6_DEVKITC_1 = Board(
ESP32_S3_DEVKITC_1 = Board( ESP32_S3_DEVKITC_1 = Board(
board_name="esp32s3", board_name="esp32s3",
real_board_name="seeed_xiao_esp32s3", # Seeed Xiao ESP32-S3 has psram. real_board_name="seeed_xiao_esp32s3", # Seeed Xiao ESP32-S3 has psram.
platform=ESP32_IDF_5_3_PIOARDUINO, platform=ESP32_IDF_5_4_PIOARDUINO,
defines=[
"BOARD_HAS_PSRAM",
],
build_flags=[ # Reserved for future use.
"-mfix-esp32-psram-cache-issue",
"-mfix-esp32-psram-cache-strategy=memw",
],
board_partitions="huge_app.csv", # Reserved for future use. board_partitions="huge_app.csv", # Reserved for future use.
) )
@ -199,6 +195,13 @@ ESP32_H2_DEVKITM_1 = Board(
platform=ESP32_IDF_5_3_PIOARDUINO, 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( ADA_FEATHER_NRF52840_SENSE = Board(
board_name="adafruit_feather_nrf52840_sense", board_name="adafruit_feather_nrf52840_sense",
platform="nordicnrf52", platform="nordicnrf52",

View 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

View file

@ -33,7 +33,7 @@ def main() -> int:
board_dir = board_dirs[which] board_dir = board_dirs[which]
# build_info_json = board_dir / "build_info.json" # build_info_json = board_dir / "build_info.json"
optimization_report = board_dir / "optimization_report.txt" 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) print(text)
return 0 return 0

View file

@ -1,14 +1,17 @@
import os import os
import unittest 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 from ci.paths import PROJECT_ROOT
SRC_ROOT = PROJECT_ROOT / "src" 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 ENABLE_PARANOID_GNU_HEADER_INSPECTION = False
@ -17,8 +20,7 @@ if ENABLE_PARANOID_GNU_HEADER_INSPECTION:
else: else:
BANNED_HEADERS_ESP = [] BANNED_HEADERS_ESP = []
BANNED_HEADERS_COMMON = [
BANNED_HEADERS_CORE = [
"assert.h", "assert.h",
"iostream", "iostream",
"stdio.h", "stdio.h",
@ -56,76 +58,123 @@ BANNED_HEADERS_CORE = [
"cstdint", "cstdint",
"cstddef", # this certainally fails "cstddef", # this certainally fails
"type_traits", # 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): class TestNoBannedHeaders(unittest.TestCase):
def check_file(self, file_path: str) -> list[str]: def test_no_banned_headers_src(self) -> None:
failings: list[str] = [] """Searches through the program files to check for banned headers."""
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:
for line_number, line in enumerate(f, 1): def on_fail(msg: str) -> None:
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)
self.fail( self.fail(
msg + "\n" msg + "\n"
"You can add '// ok include' at the end of the line to silence this error for specific inclusions." "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__": if __name__ == "__main__":

View file

@ -48,7 +48,7 @@ PROJECT_NAME = FastLED
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # 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 # 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 # for a project that appears at the top of each page and should give viewers a

View file

@ -3,10 +3,31 @@
/// @author Stefan Petrick /// @author Stefan Petrick
/// @author Zach Vorhies (FastLED adaptation) /// @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. This demo is best viewed using the FastLED compiler.
/// 3. Run the FastLED web compiler at root: `fastled`
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> #include <stdio.h>
@ -34,6 +55,10 @@ using namespace fl;
#define FIRST_ANIMATION POLAR_WAVES #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]; CRGB leds[NUM_LEDS];
XYMap xyMap = XYMap::constructRectangularGrid(MATRIX_WIDTH, MATRIX_HEIGHT); 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); UISlider brightness("Brightness", 255, 0, 255);
UINumberField fxIndex("Animartrix - index", 0, 0, NUM_ANIMATIONS - 1); UINumberField fxIndex("Animartrix - index", 0, 0, NUM_ANIMATIONS - 1);
UINumberField colorOrder("Color Order", 0, 0, 5);
UISlider timeSpeed("Time Speed", 1, -10, 10, .1); UISlider timeSpeed("Time Speed", 1, -10, 10, .1);
Animartrix animartrix(xyMap, FIRST_ANIMATION); Animartrix animartrix(xyMap, FIRST_ANIMATION);
FxEngine fxEngine(NUM_LEDS); FxEngine fxEngine(NUM_LEDS);
void setup() { void setup() {
auto screen_map = xyMap.toScreenMap(); auto screen_map = xyMap.toScreenMap();
screen_map.setDiameter(.1); screen_map.setDiameter(LED_DIAMETER);
FastLED.addLeds<WS2811, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS) FastLED.addLeds<WS2811, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection(TypicalLEDStrip) .setCorrection(TypicalLEDStrip)
.setScreenMap(screen_map); .setScreenMap(screen_map);
FastLED.setBrightness(brightness); FastLED.setBrightness(brightness);
fxEngine.addFx(animartrix); 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() { void loop() {
@ -72,3 +111,4 @@ void loop() {
} }
#endif // __AVR__

View file

@ -2,6 +2,11 @@
/* /*
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable 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 <Arduino.h>
#include <FastLED.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/audio.h"
#include "fl/downscale.h" #include "fl/downscale.h"
#include "fl/draw_visitor.h" #include "fl/draw_visitor.h"
@ -21,9 +34,11 @@ all the UI elements you see below.
#include "fl/time_alpha.h" #include "fl/time_alpha.h"
#include "fl/ui.h" #include "fl/ui.h"
#include "fl/xypath.h" #include "fl/xypath.h"
#include "fx.h" #include "fl/unused.h"
#include "fx_audio.h"
#include "fx/time.h" #include "fx/time.h"
// Sketch. // Sketch.
#include "fl/function.h" #include "fl/function.h"
@ -34,6 +49,7 @@ using namespace fl;
#define NUM_LEDS ((WIDTH) * (HEIGHT)) #define NUM_LEDS ((WIDTH) * (HEIGHT))
#define IS_SERPINTINE false #define IS_SERPINTINE false
#define TIME_ANIMATION 1000 // ms #define TIME_ANIMATION 1000 // ms
#define PIN_DATA 3
UITitle title("Simple control of an xy path"); UITitle title("Simple control of an xy path");
UIDescription description("This is more of a test for new features."); UIDescription description("This is more of a test for new features.");
@ -106,7 +122,7 @@ void setup() {
audioFadeTracker.setOutputTime(value); audioFadeTracker.setOutputTime(value);
FASTLED_WARN("Output time seconds: " << value); FASTLED_WARN("Output time seconds: " << value);
}); });
FastLED.addLeds<NEOPIXEL, 2>(leds, ledsXY.getTotal()) FastLED.addLeds<NEOPIXEL, PIN_DATA>(leds, ledsXY.getTotal())
.setScreenMap(screenmap); .setScreenMap(screenmap);
} }
@ -144,10 +160,6 @@ void loop() {
if (triggered) { if (triggered) {
FASTLED_WARN("Triggered"); FASTLED_WARN("Triggered");
} }
// fl::clear(framebuffer);
// fl::clear(framebuffer);
static uint32_t frame = 0;
// x = pointX.as_int(); // x = pointX.as_int();
y = HEIGHT / 2; y = HEIGHT / 2;
@ -164,6 +176,7 @@ void loop() {
soundLevelMeter.processBlock(sample.pcm()); soundLevelMeter.processBlock(sample.pcm());
// FASTLED_WARN("") // FASTLED_WARN("")
auto dbfs = soundLevelMeter.getDBFS(); auto dbfs = soundLevelMeter.getDBFS();
FASTLED_UNUSED(dbfs);
// FASTLED_WARN("getDBFS: " << dbfs); // FASTLED_WARN("getDBFS: " << dbfs);
int32_t max = 0; int32_t max = 0;
for (int i = 0; i < sample.pcm().size(); ++i) { for (int i = 0; i < sample.pcm().size(); ++i) {
@ -187,6 +200,7 @@ void loop() {
if (enableFFT) { if (enableFFT) {
auto max_x = fftOut.bins_raw.size() - 1; auto max_x = fftOut.bins_raw.size() - 1;
FASTLED_UNUSED(max_x);
for (int i = 0; i < fftOut.bins_raw.size(); ++i) { for (int i = 0; i < fftOut.bins_raw.size(); ++i) {
auto x = i; auto x = i;
auto v = fftOut.bins_db[i]; auto v = fftOut.bins_db[i];
@ -232,3 +246,5 @@ void loop() {
FastLED.show(); FastLED.show();
} }
#endif // __AVR__

View file

@ -1,9 +1,7 @@
#include <algorithm>
#include <cstdint>
#include <cmath>
#include <cassert>
#include "fl/time_alpha.h" #include "fl/time_alpha.h"
#include "fl/math_macros.h"
/// Tracks a smoothed peak with attack, decay, and output-inertia time-constants. /// Tracks a smoothed peak with attack, decay, and output-inertia time-constants.
class MaxFadeTracker { class MaxFadeTracker {
@ -34,8 +32,8 @@ public:
// 1) block peak // 1) block peak
float peak = 0.0f; float peak = 0.0f;
for (size_t i = 0; i < length; ++i) { for (size_t i = 0; i < length; ++i) {
float v = std::abs(samples[i]) * (1.0f/32768.0f); float v = ABS(samples[i]) * (1.0f/32768.0f);
peak = std::max(peak, v); peak = MAX(peak, v);
} }
// 2) time delta // 2) time delta
@ -43,15 +41,15 @@ public:
// 3) update currentLevel_ with attack/decay // 3) update currentLevel_ with attack/decay
if (peak > currentLevel_) { if (peak > currentLevel_) {
float riseFactor = 1.0f - std::exp(-attackRate_ * dt); float riseFactor = 1.0f - exp(-attackRate_ * dt);
currentLevel_ += (peak - currentLevel_) * riseFactor; currentLevel_ += (peak - currentLevel_) * riseFactor;
} else { } else {
float decayFactor = std::exp(-decayRate_ * dt); float decayFactor = exp(-decayRate_ * dt);
currentLevel_ *= decayFactor; currentLevel_ *= decayFactor;
} }
// 4) output inertia: smooth smoothedOutput_ → currentLevel_ // 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; smoothedOutput_ += (currentLevel_ - smoothedOutput_) * outFactor;
return smoothedOutput_; return smoothedOutput_;

View file

@ -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) #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. // Other platforms have weird issues. Will revisit this later.
void setup() {} void setup() {}
void loop() {} void loop() {}

View 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);
}

View 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
}

View 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
}

View 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
}

View 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

View 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();
}

View 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

View file

@ -2,6 +2,11 @@
/* /*
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable 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 <Arduino.h>
#include <FastLED.h> #include <FastLED.h>
#include "fl/downscale.h"
#include "fl/draw_visitor.h" #include "fl/draw_visitor.h"
#include "fl/math_macros.h" #include "fl/math_macros.h"
#include "fl/raster.h" #include "fl/raster.h"
@ -18,7 +24,6 @@ all the UI elements you see below.
#include "fl/ui.h" #include "fl/ui.h"
#include "fl/xypath.h" #include "fl/xypath.h"
#include "fx/time.h" #include "fx/time.h"
#include "fl/bilinear_compression.h"
// Sketch. // Sketch.
#include "src/wave.h" #include "src/wave.h"
@ -32,10 +37,11 @@ using namespace fl;
#define TIME_ANIMATION 1000 // ms #define TIME_ANIMATION 1000 // ms
CRGB leds[NUM_LEDS]; 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(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); // XYPathPtr shape = XYPath::NewRosePath(WIDTH, HEIGHT);
// Speed up writing to the super sampled waveFx by writing // 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(). WaveEffect wave_fx; // init in setup().
fl::vector<XYPathPtr> shapes = CreateXYPaths(WIDTH, HEIGHT); fl::vector<XYPathPtr> shapes = CreateXYPaths(WIDTH, HEIGHT);
XYRaster raster(WIDTH, HEIGHT); XYRaster raster(WIDTH, HEIGHT);
TimeWarp time_warp; TimeWarp time_warp;
@ -77,12 +82,12 @@ void setupUiCallbacks() {
maxAnimation.onChanged( maxAnimation.onChanged(
[](float value) { shapeProgress.set_max_clamp(maxAnimation.value()); }); [](float value) { shapeProgress.set_max_clamp(maxAnimation.value()); });
trigger.onChanged([]() { trigger.onClicked([]() {
// shapeProgress.trigger(millis()); // shapeProgress.trigger(millis());
FASTLED_WARN("Trigger pressed"); FASTLED_WARN("Trigger pressed");
}); });
useWaveFx.onChanged([](bool on) { useWaveFx.onChanged([](fl::UICheckbox &checkbox) {
if (on) { if (checkbox.value()) {
FASTLED_WARN("WaveFX enabled"); FASTLED_WARN("WaveFX enabled");
} else { } else {
FASTLED_WARN("WaveFX disabled"); FASTLED_WARN("WaveFX disabled");
@ -94,7 +99,8 @@ void setup() {
Serial.begin(115200); Serial.begin(115200);
auto screenmap = xyMap.toScreenMap(); auto screenmap = xyMap.toScreenMap();
screenmap.setDiameter(.2); 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(); auto screenmap2 = xyMap_Dst.toScreenMap();
screenmap.setDiameter(.5); screenmap.setDiameter(.5);
screenmap2.addOffsetY(-HEIGHT / 2); screenmap2.addOffsetY(-HEIGHT / 2);
@ -142,9 +148,6 @@ void loop() {
s_prev_alpha = curr_alpha; s_prev_alpha = curr_alpha;
} }
const bool is_active =
true || curr_alpha < maxAnimation.value() && curr_alpha > 0.0f;
static uint32_t frame = 0; static uint32_t frame = 0;
frame++; frame++;
clearLeds(); clearLeds();
@ -171,9 +174,6 @@ void loop() {
} }
uint8_t alpha = uint8_t alpha =
fl::map_range<uint8_t>(i, 0.0f, number_of_steps - 1, 64, 255); 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); Tile2x2_u8 subpixel = shape->at_subpixel(a);
subpixel.scale(alpha); subpixel.scale(alpha);
// subpixels.push_back(subpixel); // subpixels.push_back(subpixel);
@ -182,8 +182,7 @@ void loop() {
s_prev_alpha = curr_alpha; s_prev_alpha = curr_alpha;
if (useWaveFx) {
if (useWaveFx && is_active) {
DrawRasterToWaveSimulator draw_wave_fx(&wave_fx); DrawRasterToWaveSimulator draw_wave_fx(&wave_fx);
raster.draw(xyMap, draw_wave_fx); raster.draw(xyMap, draw_wave_fx);
} else { } else {

View 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

View file

@ -22,7 +22,7 @@ struct WaveEffect {
struct DrawRasterToWaveSimulator { struct DrawRasterToWaveSimulator {
DrawRasterToWaveSimulator(WaveEffect* wave_fx) : mWaveFx(wave_fx) {} 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; float valuef = value / 255.0f;
int xx = pt.x; int xx = pt.x;
int yy = pt.y; int yy = pt.y;

View file

@ -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. /// 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 /// Originally from: https://github.com/hpwit/I2SClockLessLedDriveresp32s3
/// ///
///
/// This is an advanced driver. It has certain ramifications. /// 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 /// - 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 /// this hold the reset button and release when the flash tool is looking for an
/// an upload port. /// 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 /// - 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. /// but just be aware of it. If your device suddenly stops working, 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.
/// ///
/// Is RGBW supported? Yes. /// 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? /// Why use this?
/// - FastLED api is more user friendly since you don't have to combine all your leds into one rectangular block. /// Raw YVes driver needs a perfect parallel rectacngle buffer for operation. In this code we've provided FastLED
/// - FastLED api allows you to have different sized strips which will be upscaled to the largest strip internally. /// type bindings.
///
/// 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.
/// ///
// ArduinoIDE
// Should already be enabled.
//
// PLATFORMIO BUILD FLAGS: // PLATFORMIO BUILD FLAGS:
// Define your platformio.ini like so: // Define your platformio.ini like so:
// //
// PlatformIO
// [env:esp32s3] // [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 // framework = arduino
// board = seeed_xiao_esp32s3 // 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 "FastLED.h"
#include "fl/assert.h" #include "fl/assert.h"
@ -66,6 +46,8 @@
#define NUM_LEDS_PER_STRIP 256 #define NUM_LEDS_PER_STRIP 256
#define NUM_LEDS (NUM_LEDS_PER_STRIP * NUMSTRIPS) #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_DATA0 19 // B0
#define EXAMPLE_PIN_NUM_DATA1 45 // B1 #define EXAMPLE_PIN_NUM_DATA1 45 // B1
#define EXAMPLE_PIN_NUM_DATA2 21 // B2 #define EXAMPLE_PIN_NUM_DATA2 21 // B2
@ -84,8 +66,9 @@
#define EXAMPLE_PIN_NUM_DATA15 18 // R4 #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[] = { int PINS[] = {
EXAMPLE_PIN_NUM_DATA0, EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1, EXAMPLE_PIN_NUM_DATA1,
@ -105,10 +88,9 @@ int PINS[] = {
EXAMPLE_PIN_NUM_DATA15 EXAMPLE_PIN_NUM_DATA15
}; };
fl::InternalI2SDriver *driver = nullptr;
CRGB leds[NUM_LEDS]; 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. // 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. // 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>( 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>( FastLED.addLeds<WS2812, EXAMPLE_PIN_NUM_DATA15, GRB>(
leds + (15 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP leds + (15 * NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP
); );
FastLED.setBrightness(32);
} }
void setup() { 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: // 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(). // 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()); 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("Total PSRAM: %d", ESP.getPsramSize()); // If this prints out 0, then PSRAM is not enabled.
log_d("Free PSRAM: %d", ESP.getFreePsram()); 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. 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(); setup_i2s();
} else { FastLED.setBrightness(32);
driver = fl::InternalI2SDriver::create();
driver->initled((uint8_t *)leds, PINS, NUMSTRIPS, NUM_LEDS_PER_STRIP); // Skips extra frame buffer copy.
driver->setBrightness(32);
}
} }
void fill_rainbow(CRGB* all_leds) { void fill_rainbow(CRGB* all_leds) {
@ -199,10 +176,6 @@ void fill_rainbow(CRGB* all_leds) {
void loop() { void loop() {
fill_rainbow(leds); fill_rainbow(leds);
if (gUseFastLEDApi) { FastLED.show();
FastLED.show();
} else {
FASTLED_ASSERT(driver != nullptr, "Did not expect driver to be null");
driver->show();
}
} }

View file

@ -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

View file

@ -1,112 +1,64 @@
/* /*
Festival Stick is a dense corkscrew of LEDs that is wrapped around one end of Basic cork screw test.
a wooden walking stick commonly found on amazon.A0
The UI screenmap projects this cork screw into polar coordinates, so that the This test is forward mapping, in which we test that
LEDs are mapped to a sprial, with the inner portion of the spiral being the top, the corkscrew is mapped to cylinder cartesian coordinates.
the outer most portion being the bottom.
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/assert.h"
#include "fl/corkscrew.h" #include "fl/corkscrew.h"
#include "fl/screenmap.h" #include "fl/grid.h"
#include "fl/warn.h"
#include "fl/sstream.h"
#include "fl/leds.h" #include "fl/leds.h"
#include "fl/screenmap.h"
#include "fl/sstream.h"
#include "fl/warn.h"
#include "noise.h" #include "noise.h"
#include <FastLED.h> #include <FastLED.h>
// #include "vec3.h" // #include "vec3.h"
using namespace fl; using namespace fl;
// Power management settings
#define VOLTS 5
#define MAX_AMPS 1
#define PIN_DATA 9 #define PIN_DATA 3
#define PIN_CLOCK 7 #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 NUM_LEDS 288
#define CORKSCREW_TOTAL_LENGTH 100 // 100 cm
#define CORKSCREW_TOTAL_HEIGHT \ #define CORKSCREW_TOTAL_HEIGHT \
23.25f // Total height of the corkscrew in centimeters for 144 densly 23.25 // when height = 0, it's a circle.
// wrapped up over 19 turns // wrapped up over 19 turns
#define CORKSCREW_TURNS 19 // Default to 19 turns #define CORKSCREW_TURNS 20.5 // Default to 19 turns
// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs // #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs
// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter // #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter
#define CORKSCREW_WIDTH 16 UITitle festivalStickTitle("Corkscrew");
#define CORKSCREW_HEIGHT 19
UITitle festivalStickTitle("Festival Stick");
UIDescription festivalStickDescription( UIDescription festivalStickDescription(
"Take a wooden walking stick, wrap dense LEDs around it like a corkscrew. " "Tests the ability to map a cork screw onto a 2D cylindrical surface");
"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); UISlider speed("Speed", 0.1f, 0.01f, 1.0f, 0.01f);
UIButton button("Button");
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 // 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. // with 23.25cm height, 19 turns, and ~15.5 LEDs per turn.
Corkscrew::Input Corkscrew::Input corkscrewInput(CORKSCREW_TOTAL_LENGTH, CORKSCREW_TOTAL_HEIGHT,
corkscrewInput(CORKSCREW_TOTAL_HEIGHT, CORKSCREW_TURNS, NUM_LEDS, 0);
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::Output corkscrewMap = fl::Corkscrew::generateMap(corkscrewInput); // Corkscrew::State corkscrewMap = fl::Corkscrew::generateMap(corkscrewInput);
Corkscrew corkscrew(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: // Create a corkscrew with:
// - 30cm total length (300mm) // - 30cm total length (300mm)
// - 5cm width (50mm) // - 5cm width (50mm)
@ -117,96 +69,77 @@ fl::ScreenMap makeScreenMap(corkscrew_args args = corkscrew_args()) {
// fl::vector<vec3f> mapCorkScrew = makeCorkScrew(args); // fl::vector<vec3f> mapCorkScrew = makeCorkScrew(args);
fl::ScreenMap screenMap; 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() { void setup() {
pinMode(PIN_GRND, OUTPUT); int width = corkscrew.cylinder_width();
digitalWrite(PIN_GRND, LOW); // Set ground pin to low int height = corkscrew.cylinder_height();
button.addRealButton(Button(PIN_BUTTON));
corkscrew_args args = corkscrew_args(); frameBuffer.reset(width, height);
screenMap = makeScreenMap(args); XYMap xyMap = XYMap::constructRectangularGrid(width, height, 0);
// screenMap = ScreenMap::Circle(NUM_LEDS, 1.5f, 0.5f, 1.0f);
auto controller = addController(); 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 // Set the screen map for the controller
controller->setScreenMap(screenMap); 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() { void loop() {
uint32_t now = millis(); uint32_t now = millis();
fl::clear(leds); // fl::clear(lesdds);
fl::clear(frameBuffer); fl::clear(frameBuffer);
static int w = 0; static float pos = 0;
EVERY_N_MILLIS(300) { // Update the corkscrew mapping every second
// Update the corkscrew mapping every second // w = (w + 1) % CORKSCREW_WIDTH;
w = (w + 1) % CORKSCREW_WIDTH; // frameBuffer.
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
// draw a blue line down the middle if (alpha > 0) { // Only draw if there's some alpha
for (int i = 0; i < CORKSCREW_HEIGHT; ++i) { CRGB c = color;
frameBuffer.at(w % CORKSCREW_WIDTH, i) = CRGB::Blue; c.nscale8(alpha); // Scale the color by the alpha value
frameBuffer.at((w + 1) % CORKSCREW_WIDTH, i) = CRGB::Blue; frameBuffer.at(wrapped_pos.x, wrapped_pos.y) = c;
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; }
} 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)
} }
// 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);
}
FastLED.show(); FastLED.show();
} }

View file

@ -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." "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."); "This assumes the dense 144 LEDs / meter.");
UISlider ledsScale("Leds scale", 0.1f, 0.1f, 1.0f, 0.01f); UISlider ledsScale("Leds scale", 0.1f, 0.1f, 1.0f, 0.01f);
UIButton button("Button"); UIButton button("Button");
// Adding a brightness slider
UISlider brightness("Brightness", 16, 0, 255, 1); // Brightness from 0 to 255
CRGB leds[NUM_LEDS]; CRGB leds[NUM_LEDS];
@ -166,6 +166,8 @@ void setup() {
// into account the APA102HD gamma correction. However it is still a correct upper bound // 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. // that will match the ledset exactly when the display tries to go full white.
FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_AMPS * 1000); FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_AMPS * 1000);
// set brightness 8
FastLED.setBrightness(brightness.as_int());
button.onChanged([](UIButton& but) { button.onChanged([](UIButton& but) {
// This function is called when the button is pressed // This function is called when the button is pressed
// If the button is pressed, show the generative pattern // If the button is pressed, show the generative pattern

View 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();
}

View file

@ -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 "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 #if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
// Cylinder dimensions - this defines the size of our virtual LED grid void setup() {}
#define HEIGHT 100 // Number of rows in the cylinder (vertical dimension) void loop() {}
#define WIDTH 100 // Number of columns in the cylinder (circumference) #else
#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts) #include "FireCylinder.h"
#define BRIGHTNESS 255 // Maximum brightness level (0-255) #endif
// 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();
}

View 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

View file

@ -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 "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 #if !SKETCH_HAS_LOTS_OF_MEMORY
// Platform does not have enough memory
// Matrix dimensions - this defines the size of our virtual LED grid void setup() {}
#define HEIGHT 100 // Number of rows in the matrix void loop() {}
#define WIDTH 100 // Number of columns in the matrix #else
#define SERPENTINE true // Whether the LED strip zigzags back and forth (common in matrix layouts) #include "FireMatrix.h"
#define BRIGHTNESS 255 // Maximum brightness level (0-255) #endif
// 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();
}

View file

@ -8,8 +8,9 @@
/// 3. Run the FastLED web compiler at root: `fastled` /// 3. Run the FastLED web compiler at root: `fastled`
/// 4. When the compiler is done a web page will open. /// 4. When the compiler is done a web page will open.
#include "FastLED.h"
#ifdef __AVR__ #if !SKETCH_HAS_LOTS_OF_MEMORY
void setup() { void setup() {
// put your setup code here, to run once: // put your setup code here, to run once:
} }

View file

@ -2,6 +2,11 @@
/* /*
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable This will compile and preview the sketch in the browser, and enable

View file

@ -1,11 +1,11 @@
#pragma once
/* /*
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable This will compile and preview the sketch in the browser, and enable
@ -40,6 +40,7 @@ CRGB leds[NUM_LEDS];
UITitle title("FxWave2D Demo"); UITitle title("FxWave2D Demo");
UIDescription description("Advanced layered and blended wave effects."); 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: // Main control UI elements:
UIButton button("Trigger"); // Button to trigger a single ripple UIButton button("Trigger"); // Button to trigger a single ripple
UIButton buttonFancy("Trigger Fancy"); // Button to trigger a fancy cross-shaped effect 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 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) 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: // Upper wave layer controls:
UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f); // How fast the upper wave propagates UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f); // How fast the upper wave propagates

View file

@ -5,6 +5,11 @@
/* /*
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable This will compile and preview the sketch in the browser, and enable

View 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);
}

View file

@ -1,90 +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
/// @file NoisePlayground.ino // don't have enough memory to handle this complex animation.
/// @brief Demonstrates how to use noise generation on a 2D LED matrix // Instead, we provide empty setup/loop functions so the sketch will compile but do nothing.
/// @example NoisePlayground.ino void setup() {}
void loop() {}
#include <FastLED.h> #else // For all other platforms with more memory (ESP32, Teensy, etc.)
#include "NoisePlayground.h"
#if defined(__AVR__) #endif // End of the non-AVR code section
// 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

View 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

View file

@ -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 #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 compile this for AVR microcontrollers (like Arduino Uno) because they typically
// don't have enough memory to handle this complex animation. // 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. // Instead, we provide empty setup/loop functions so the sketch will compile but do nothing.
void setup() {} void setup() {}
void loop() {} void loop() {}
#else // For all other platforms with more memory (ESP32, Teensy, etc.) #else // For all other platforms with more memory (ESP32, Teensy, etc.)
#include "NoisePlusPalette.h"
// 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 #endif // End of the non-AVR code section

View file

@ -1,8 +1,9 @@
/// @file Overclock.ino /// @file Overclock.ino
/// @brief Demonstrates how to overclock a FastLED setup /// @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 // To effectively test the overclock feature we need
// a large enough dataset to test against. Unfortunately // a large enough dataset to test against. Unfortunately
// the avr platforms don't have enough memory so this example // the avr platforms don't have enough memory so this example

View file

@ -8,9 +8,6 @@
// Animated, ever-changing rainbows. // Animated, ever-changing rainbows.
// by Mark Kriegsman // 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 DATA_PIN 3
//#define CLK_PIN 4 //#define CLK_PIN 4

View file

@ -4,6 +4,11 @@
This is a 1D wave simluation! This is a 1D wave simluation!
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable This will compile and preview the sketch in the browser, and enable

View file

@ -2,6 +2,11 @@
/* /*
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable This will compile and preview the sketch in the browser, and enable

View file

@ -2,6 +2,11 @@
/* /*
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable This will compile and preview the sketch in the browser, and enable

View file

@ -2,6 +2,11 @@
/* /*
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable 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 "src/xypaths.h"
#include "fl/function.h" #include "fl/function.h"
#include <assert.h>
using namespace fl; using namespace fl;
@ -37,8 +41,6 @@ using namespace fl;
#define IS_SERPINTINE true #define IS_SERPINTINE true
#define TIME_ANIMATION 1000 // ms #define TIME_ANIMATION 1000 // ms
// CRGB leds[NUM_LEDS];
LedsXY<WIDTH, HEIGHT> leds; LedsXY<WIDTH, HEIGHT> leds;
XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE);
UITitle title("Simple control of an xy path"); 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 steps("Steps", 100.0f, 1.0f, 200.0f, 1.0f);
UISlider length("Length", 1.0f, 0.0f, 1.0f, 0.01f); UISlider length("Length", 1.0f, 0.0f, 1.0f, 0.01f);
XYPathPtr heartPath = XYPath::NewHeartPath(WIDTH, HEIGHT); XYPathPtr heartPath = XYPath::NewHeartPath(WIDTH, HEIGHT);
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
auto screenmap = xyMap.toScreenMap(); auto screenmap = xyMap.toScreenMap();
screenmap.setDiameter(.2); screenmap.setDiameter(.2);
FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS).setScreenMap(screenmap); FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS).setScreenMap(screenmap);
} }
void loop() { void loop() {
fl::clear(leds); fl::clear(leds);
// FASTLED_ASSERT(false, "This is a test"); // FASTLED_ASSERT(false, "This is a test");
// leds(x,y) = CRGB(255, 0, 0); // leds(x,y) = CRGB(255, 0, 0);
float from = offset; float from = offset;
float to = length.value() + offset.value(); float to = length.value() + offset.value();
heartPath->drawColor(CRGB(255, 0, 0), from, to, &leds, steps.as_int()); heartPath->drawColor(CRGB(255, 0, 0), from, to, &leds, steps.as_int());
FastLED.show(); FastLED.show();
} }

View file

@ -2,6 +2,11 @@
/* /*
This demo is best viewed using the FastLED compiler. This demo is best viewed using the FastLED compiler.
Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
Python
Install: pip install fastled Install: pip install fastled
Run: fastled <this sketch directory> Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable This will compile and preview the sketch in the browser, and enable

View file

@ -38,7 +38,7 @@
"type": "git", "type": "git",
"url": "https://github.com/FastLED/FastLED.git" "url": "https://github.com/FastLED/FastLED.git"
}, },
"version": "3.9.20", "version": "3.10.1",
"license": "MIT", "license": "MIT",
"homepage": "http://fastled.io", "homepage": "http://fastled.io",
"frameworks": "arduino", "frameworks": "arduino",

View file

@ -1,5 +1,5 @@
name=FastLED name=FastLED
version=3.9.20 version=3.10.1
author=Daniel Garcia author=Daniel Garcia
maintainer=Daniel Garcia <dgarcia@fastled.io> 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. sentence=Multi-platform library for controlling dozens of different types of LEDs along with optimized math, effect, and noise functions.

View file

@ -5,7 +5,7 @@ default_envs = dev
[env:generic-esp] [env:generic-esp]
# Developement branch of the open source espressif32 platform # 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 framework = arduino
upload_protocol = esptool upload_protocol = esptool
monitor_filters = monitor_filters =
@ -41,9 +41,6 @@ extends = env:generic-esp
board = seeed_xiao_esp32s3 board = seeed_xiao_esp32s3
build_flags = build_flags =
${env:generic-esp.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 board_build.partitions = huge_app.csv
[env:esp32c6] [env:esp32c6]

View file

@ -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 * Hotfixes for AVR platforms for 3.9.17

View file

@ -57,7 +57,7 @@ uint8_t get_brightness();
void *pSmartMatrix = NULL; void *pSmartMatrix = NULL;
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_PUSH
FL_DISABLE_WARNING(global-constructors) FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
CFastLED FastLED; // global constructor allowed in this case. CFastLED FastLED; // global constructor allowed in this case.

View file

@ -16,13 +16,13 @@
/// * 1 digit for the major version /// * 1 digit for the major version
/// * 3 digits for the minor version /// * 3 digits for the minor version
/// * 3 digits for the patch version /// * 3 digits for the patch version
#define FASTLED_VERSION 3009020 #define FASTLED_VERSION 301001
#ifndef FASTLED_INTERNAL #ifndef FASTLED_INTERNAL
# ifdef FASTLED_SHOW_VERSION # ifdef FASTLED_SHOW_VERSION
# ifdef FASTLED_HAS_PRAGMA_MESSAGE # ifdef FASTLED_HAS_PRAGMA_MESSAGE
# pragma message "FastLED version 3.009.020" # pragma message "FastLED version 3.010.001"
# else # 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 # endif
#endif #endif
@ -968,3 +968,6 @@ using namespace fl;
void loop() { FASTLED_WARN("hijacked the loop"); real_loop(); } \ void loop() { FASTLED_WARN("hijacked the loop"); real_loop(); } \
void real_loop() void real_loop()
#endif #endif
#include "fl/sketch_macros.h"

View file

@ -6,7 +6,7 @@
#include "FastLED.h" #include "FastLED.h"
#include "fl/xymap.h" #include "fl/xymap.h"
#include "fl/bilinear_expansion.h" #include "fl/upscale.h"
#include "fl/downscale.h" #include "fl/downscale.h"
#include "lib8tion/math8.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"); "Upscaling only works with a src matrix that is rectangular");
uint16_t w = srcXY.getWidth(); uint16_t w = srcXY.getWidth();
uint16_t h = srcXY.getHeight(); 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) { CRGB &CRGB::nscale8(uint8_t scaledown) {

View file

@ -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 } // namespace fl

View file

@ -175,7 +175,7 @@ void swap(array<T, N> &lhs,
TYPE *NAME = reinterpret_cast<TYPE *>(alloca(sizeof(TYPE) * (SIZE))); \ TYPE *NAME = reinterpret_cast<TYPE *>(alloca(sizeof(TYPE) * (SIZE))); \
memset(NAME, 0, sizeof(TYPE) * (SIZE)) memset(NAME, 0, sizeof(TYPE) * (SIZE))
#elif __has_include(<cstdlib>) #elif __has_include(<cstdlib>)
#include <cstdlib> #include <cstdlib> // ok include
#define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \ #define FASTLED_STACK_ARRAY(TYPE, NAME, SIZE) \
TYPE *NAME = reinterpret_cast<TYPE *>(alloca(sizeof(TYPE) * (SIZE))); \ TYPE *NAME = reinterpret_cast<TYPE *>(alloca(sizeof(TYPE) * (SIZE))); \
memset(NAME, 0, sizeof(TYPE) * (SIZE)) memset(NAME, 0, sizeof(TYPE) * (SIZE))

View file

@ -7,7 +7,9 @@
#include <stdint.h> #include <stdint.h>
#include "crgb.h" #include "crgb.h"
#include "fl/deprecated.h"
#include "fl/namespace.h" #include "fl/namespace.h"
#include "fl/upscale.h"
#include "fl/xymap.h" #include "fl/xymap.h"
namespace fl { namespace fl {
@ -19,47 +21,62 @@ namespace fl {
/// @param inputHeight The height 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 /// @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. /// pixel is mapped outside of the range then it is clipped.
void bilinearExpandArbitrary(const CRGB *input, CRGB *output, void bilinearExpandArbitrary(const CRGB *input, CRGB *output,
uint16_t inputWidth, uint16_t inputHeight, 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, 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");
// void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth,
inline void bilinearExpand(const CRGB *input, CRGB *output, uint16_t inputWidth, uint16_t inputHeight, fl::XYMap xyMap)
uint16_t inputHeight, fl::XYMap xyMap) { FASTLED_DEPRECATED("use upscale from upscale.h");
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);
}
}
// 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, 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, void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
uint16_t inputWidth, uint16_t inputHeight, 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 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 } // namespace fl

View file

@ -3,7 +3,12 @@
#include "fl/leds.h" #include "fl/leds.h"
#include "fl/stdint.h" #include "fl/stdint.h"
namespace fl { namespace fl {
template<typename T>
class Grid;
// Memory safe clear function for CRGB arrays. // Memory safe clear function for CRGB arrays.
template <int N> inline void clear(CRGB (&arr)[N]) { template <int N> inline void clear(CRGB (&arr)[N]) {
for (int i = 0; i < N; ++i) { for (int i = 0; i < N; ++i) {
@ -18,4 +23,17 @@ inline void clear(LedsXY<W, H> &leds) {
leds.fill(CRGB::Black); 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 } // namespace fl

View file

@ -19,3 +19,11 @@
#define FL_DISABLE_WARNING_POP #define FL_DISABLE_WARNING_POP
#define FL_DISABLE_WARNING(warning) #define FL_DISABLE_WARNING(warning)
#endif #endif
#if defined(__clang__)
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS \
FL_DISABLE_WARNING(global-constructors)
#else
#define FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS /* nothing */
#endif

View file

@ -1,138 +1,142 @@
#include "fl/corkscrew.h" #include "fl/corkscrew.h"
#include "fl/algorithm.h" #include "fl/algorithm.h"
#include "fl/assert.h"
#include "fl/math.h" #include "fl/math.h"
#include "fl/splat.h" #include "fl/splat.h"
#include "fl/math_macros.h"
#define TWO_PI (PI * 2.0) #define TWO_PI (PI * 2.0)
namespace fl { 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) { void generateState(const Corkscrew::Input &input, CorkscrewState *output) {
// Calculate circumference per turn from height and total angle
float circumferencePerTurn = input.totalHeight * TWO_PI / input.totalAngle;
// Calculate vertical segments based on number of turns // Calculate vertical segments based on number of turns
// For a single turn (2π), we want exactly 1 vertical segment // For a single turn (2π), we want exactly 1 vertical segment
// For two turns (4π), we want exactly 2 vertical segments // 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 // 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->mapping.clear();
output.height = verticalSegments; output->width = 0; // we will change this below.
output.width = ceil(ledsPerTurn); output->height = 0;
output.mapping.clear();
// If numLeds is specified, use that for mapping size instead of grid // 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) {
for (uint16_t i = 0; i < input.numLeds; ++i) { // Calculate position along the corkscrew (0.0 to 1.0)
// Calculate position along the corkscrew (0.0 to 1.0) const float i_f = static_cast<float>(i);
float position = static_cast<float>(i) / (input.numLeds - 1); 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 if (!output->mapping.empty()) {
float angle = position * input.totalAngle; float max_width = 0.0f;
float height = position * verticalSegments; float max_height = 0.0f;
for (const auto &point : output->mapping) {
// Calculate circumference position max_width = MAX(max_width, point.x);
float circumference = fmodf(angle * circumferencePerTurn / TWO_PI, max_height = MAX(max_height, point.y);
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);
}
} }
output->width = static_cast<uint16_t>(ceilf(max_width)) + 1;
output->height = static_cast<uint16_t>(ceilf(max_height)) + 1;
} }
// Apply inversion if requested // Apply inversion if requested
if (input.invert) { 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) { Corkscrew::Corkscrew(const Corkscrew::Input &input) : mInput(input) {
fl::generateMap(mInput, mOutput); fl::generateState(mInput, &mState);
} }
vec2f Corkscrew::at(uint16_t i) const { vec2f Corkscrew::at_exact(uint16_t i) const {
if (i >= mOutput.mapping.size()) { if (i >= mState.mapping.size()) {
// Handle out-of-bounds access, possibly by returning a default value // Handle out-of-bounds access, possibly by returning a default value
return vec2f(0, 0); return vec2f(0, 0);
} }
// Convert the float position to integer // Convert the float position to integer
const vec2f &position = mOutput.mapping[i]; const vec2f &position = mState.mapping[i];
return position; 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 // Handle out-of-bounds access, possibly by returning a default
// Tile2x2_u8 // Tile2x2_u8
FASTLED_ASSERT(false, "Out of bounds access in Corkscrew at_splat: "
<< i << " size: " << mState.mapping.size());
return Tile2x2_u8(); return Tile2x2_u8();
} }
// Use the splat function to convert the vec2f to a 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) { Corkscrew::State Corkscrew::generateState(const Corkscrew::Input &input) {
CorkscrewOutput output; CorkscrewState output;
fl::generateMap(input, output); fl::generateState(input, &output);
return output; return output;
} }
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 } // namespace fl

View file

@ -4,15 +4,15 @@
* @file corkscrew.h * @file corkscrew.h
* @brief Corkscrew projection utilities * @brief Corkscrew projection utilities
* *
* Corkscrew projection maps from Corkscrew (θ, h) to Cylindrical cartesian (w, * You want to draw on a rectangular surface, and have it map to a GOD DAMN
* h) space, where w = one turn of the Corkscrew. The corkscrew at (0,0) will * CORKSCREW! Well guess what, this is the file for you.
* map to (0,0) in cylindrical space. *
* 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: * Inputs:
* - Total Height of the Corkscrew in centimeters * - Total Height of the Corkscrew in centimeters
@ -35,6 +35,7 @@
#include "fl/allocator.h" #include "fl/allocator.h"
#include "fl/geometry.h" #include "fl/geometry.h"
#include "fl/math_macros.h" #include "fl/math_macros.h"
#include "fl/pair.h"
#include "fl/tile2x2.h" #include "fl/tile2x2.h"
#include "fl/vector.h" #include "fl/vector.h"
@ -46,41 +47,43 @@ namespace fl {
* @return The resulting cylindrical mapping. * @return The resulting cylindrical mapping.
*/ */
struct CorkscrewInput { 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 float totalHeight = 23.25; // Total height of the corkscrew in centimeters
// for 144 densly wrapped up over 19 turns // for 144 densly wrapped up over 19 turns
float totalAngle = 19.f * 2 * PI; // Default to 19 turns float totalTurns = 19.f; // Default to 19 turns
float offsetCircumference = 0; // Optional offset for gap accounting float offsetCircumference = 0; // Optional offset for gap accounting
uint16_t numLeds = 144; // Default to dense 144 leds. uint16_t numLeds = 144; // Default to dense 144 leds.
bool invert = false; // If true, reverse the mapping order bool invert = false; // If true, reverse the mapping order
CorkscrewInput() = default; CorkscrewInput() = default;
CorkscrewInput(float height, float total_angle, float offset = 0, CorkscrewInput(float total_length, float height, float total_turns,
uint16_t leds = 144, bool invertMapping = false) uint16_t leds, float offset = 0,
: totalHeight(height), totalAngle(total_angle), bool invertMapping = false)
offsetCircumference(offset), numLeds(leds), invert(invertMapping) {} : 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 width = 0; // Width of cylindrical map (circumference of one turn)
uint16_t height = 0; // Height of cylindrical map (total vertical segments) uint16_t height = 0; // Height of cylindrical map (total vertical segments)
fl::vector<fl::vec2f, fl::allocator_psram<fl::vec2f>> fl::vector<fl::vec2f, fl::allocator_psram<fl::vec2f>>
mapping; // Full precision mapping from corkscrew to cylindrical mapping; // Full precision mapping from corkscrew to cylindrical
CorkscrewOutput() = default; CorkscrewState() = default;
class iterator { class iterator {
public: public:
using value_type = vec2f; using value_type = vec2f;
using difference_type = int32_t; using difference_type = int32_t;
using pointer = vec2f*; using pointer = vec2f *;
using reference = vec2f&; using reference = vec2f &;
iterator(CorkscrewOutput* owner, size_t position) iterator(CorkscrewState *owner, size_t position)
: owner_(owner), position_(position) {} : owner_(owner), position_(position) {}
vec2f& operator*() const { vec2f &operator*() const { return owner_->mapping[position_]; }
return owner_->mapping[position_];
}
iterator& operator++() { iterator &operator++() {
++position_; ++position_;
return *this; return *this;
} }
@ -91,67 +94,82 @@ struct CorkscrewOutput {
return temp; 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_; return position_ == other.position_;
} }
bool operator!=(const iterator& other) const { bool operator!=(const iterator &other) const {
return position_ != other.position_; return position_ != other.position_;
} }
private: difference_type operator-(const iterator &other) const {
CorkscrewOutput* owner_; return static_cast<difference_type>(position_) -
static_cast<difference_type>(other.position_);
}
private:
CorkscrewState *owner_;
size_t position_; size_t position_;
}; };
iterator begin() { iterator begin() { return iterator(this, 0); }
return iterator(this, 0);
}
iterator end() { iterator end() { return iterator(this, mapping.size()); }
return iterator(this, mapping.size());
}
fl::Tile2x2_u8 at(int16_t x, int16_t y) const;
}; };
// Maps a Corkscrew defined by the input to a cylindrical mapping for rendering
// a densly wrapped LED corkscrew.
class Corkscrew { class Corkscrew {
public: public:
using Input = CorkscrewInput; using Input = CorkscrewInput;
using Output = CorkscrewOutput; using State = CorkscrewState;
using iterator = CorkscrewOutput::iterator; using iterator = CorkscrewState::iterator;
Corkscrew(const Input &input); Corkscrew(const Input &input);
Corkscrew(const Corkscrew &) = default; 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; size_t size() const;
iterator begin() { iterator begin() { return mState.begin(); }
return mOutput.begin();
}
iterator end() { iterator end() { return mState.end(); }
return mOutput.end();
}
/// For testing /// For testing
static CorkscrewOutput generateMap(const Input &input); static State generateState(const Input &input);
Output& access() { State &access() { return mState; }
return mOutput;
}
const Output& access() const { const State &access() const { return mState; }
return mOutput;
} int16_t cylinder_width() const { return mState.width; }
int16_t cylinder_height() const { return mState.height; }
private: private:
Input mInput; // The input parameters defining the corkscrew // For internal use. Splats the pixel on the surface which
CorkscrewOutput mOutput; // The resulting cylindrical mapping // 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 } // namespace fl

View file

@ -13,19 +13,15 @@ template <typename T> class Grid {
Grid(uint32_t width, uint32_t height) { reset(width, height); } Grid(uint32_t width, uint32_t height) { reset(width, height); }
void reset(uint32_t width, uint32_t height) { void reset(uint32_t width, uint32_t height) {
clear();
if (width != mWidth || height != mHeight) { if (width != mWidth || height != mHeight) {
mWidth = width; mWidth = width;
mHeight = height; mHeight = height;
// Only re-allocate if the size is now bigger. mData.resize(width * height);
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);
} }
clear(); mSlice = fl::MatrixSlice<T>(mData.data(), width, height, 0, 0,
width, height);
} }
void clear() { void clear() {
@ -60,6 +56,11 @@ template <typename T> class Grid {
uint32_t width() const { return mWidth; } uint32_t width() const { return mWidth; }
uint32_t height() const { return mHeight; } 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: private:
static T &NullValue() { static T &NullValue() {
static T gNull; static T gNull;

View 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

View file

@ -9,6 +9,8 @@ namespace fl {
template <typename T, int N = 0> class Singleton { template <typename T, int N = 0> class Singleton {
public: public:
static T &instance() { 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; static T instance;
return instance; return instance;
} }

View 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

View file

@ -1,16 +1,27 @@
#include "fl/tile2x2.h" #include "fl/tile2x2.h"
#include "crgb.h" #include "crgb.h"
#include "fl/draw_visitor.h" #include "fl/draw_visitor.h"
#include "fl/math_macros.h"
#include "fl/raster.h" #include "fl/raster.h"
#include "fl/raster_sparse.h" #include "fl/raster_sparse.h"
#include "fl/unused.h" #include "fl/unused.h"
#include "fl/warn.h" #include "fl/warn.h"
#include "fl/xymap.h" #include "fl/xymap.h"
using namespace fl;
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, void Tile2x2_u8::Rasterize(const Slice<const Tile2x2_u8> &tiles,
XYRasterU8Sparse *out_raster) { XYRasterU8Sparse *out_raster) {
out_raster->rasterize(tiles); 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 } // namespace fl

View file

@ -5,6 +5,7 @@
#include "fl/geometry.h" #include "fl/geometry.h"
#include "fl/namespace.h" #include "fl/namespace.h"
#include "fl/pair.h"
#include "fl/slice.h" #include "fl/slice.h"
#include "fl/xymap.h" #include "fl/xymap.h"
@ -31,6 +32,8 @@ class Tile2x2_u8 {
void scale(uint8_t scale); 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 &operator()(int x, int y) { return at(x, y); }
uint8_t &at(int x, int y) { return mTile[y][x]; } uint8_t &at(int x, int y) { return mTile[y][x]; }
const uint8_t &at(int x, int y) const { 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 &lower_right() { return at(1, 0); }
uint8_t &upper_right() { return at(1, 1); } uint8_t &upper_right() { return at(1, 1); }
uint8_t maxValue() const { const uint8_t &lower_left() const { return at(0, 0); }
uint8_t max = 0; const uint8_t &upper_left() const { return at(0, 1); }
max = MAX(max, at(0, 0)); const uint8_t &lower_right() const { return at(1, 0); }
max = MAX(max, at(0, 1)); const uint8_t &upper_right() const { return at(1, 1); }
max = MAX(max, at(1, 0));
max = MAX(max, at(1, 1));
return max;
}
static Tile2x2_u8 Max(const Tile2x2_u8 &a, const Tile2x2_u8 &b) { uint8_t maxValue() const;
Tile2x2_u8 result;
for (int x = 0; x < 2; ++x) { static Tile2x2_u8 MaxTile(const Tile2x2_u8 &a, const Tile2x2_u8 &b);
for (int y = 0; y < 2; ++y) {
result.at(x, y) = MAX(a.at(x, y), b.at(x, y));
}
}
return result;
}
vec2<int16_t> origin() const { return mOrigin; } vec2<int16_t> origin() const { return mOrigin; }
/// bounds => [begin_x, end_x) (where end_x is exclusive) /// bounds => [begin_x, end_x) (where end_x is exclusive)
rect<int16_t> bounds() const { 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);
}
// Draws the subpixel tile to the led array. // Draws the subpixel tile to the led array.
void draw(const CRGB &color, const XYMap &xymap, CRGB *out) const; void draw(const CRGB &color, const XYMap &xymap, CRGB *out) const;
@ -96,4 +85,24 @@ class Tile2x2_u8 {
vec2<int16_t> mOrigin; 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 } // namespace fl

View file

@ -4,9 +4,9 @@
#include <stdint.h> #include <stdint.h>
#include "bilinear_expansion.h"
#include "crgb.h" #include "crgb.h"
#include "fl/namespace.h" #include "fl/namespace.h"
#include "fl/upscale.h"
#include "fl/xymap.h" #include "fl/xymap.h"
namespace fl { 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 bilinearInterpolatePowerOf2(uint8_t v00, uint8_t v10, uint8_t v01,
uint8_t v11, uint8_t dx, uint8_t dy); uint8_t v11, uint8_t dx, uint8_t dy);
void bilinearExpandArbitrary(const CRGB *input, CRGB *output, void upscaleArbitrary(const CRGB *input, CRGB *output, uint16_t inputWidth,
uint16_t inputWidth, uint16_t inputHeight, uint16_t inputHeight, XYMap xyMap) {
XYMap xyMap) {
uint16_t n = xyMap.getTotal(); uint16_t n = xyMap.getTotal();
uint16_t outputWidth = xyMap.getWidth(); uint16_t outputWidth = xyMap.getWidth();
uint16_t outputHeight = xyMap.getHeight(); 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; return result;
} }
void bilinearExpandPowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth, void upscalePowerOf2(const CRGB *input, CRGB *output, uint8_t inputWidth,
uint8_t inputHeight, XYMap xyMap) { uint8_t inputHeight, XYMap xyMap) {
uint8_t width = xyMap.getWidth(); uint8_t width = xyMap.getWidth();
uint8_t height = xyMap.getHeight(); uint8_t height = xyMap.getHeight();
if (width != xyMap.getWidth() || 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 // 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) { uint8_t v11, float dx, float dy) {
float dx_inv = 1.0f - dx; float dx_inv = 1.0f - dx;
float dy_inv = 1.0f - dy; 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 // Floating-point version for arbitrary grid sizes
void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output, void upscaleArbitraryFloat(const CRGB *input, CRGB *output, uint16_t inputWidth,
uint16_t inputWidth, uint16_t inputHeight, uint16_t inputHeight, XYMap xyMap) {
XYMap xyMap) {
uint16_t n = xyMap.getTotal(); uint16_t n = xyMap.getTotal();
uint16_t outputWidth = xyMap.getWidth(); uint16_t outputWidth = xyMap.getWidth();
uint16_t outputHeight = xyMap.getHeight(); uint16_t outputHeight = xyMap.getHeight();
@ -214,11 +212,11 @@ void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
CRGB result; CRGB result;
result.r = 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 = 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 = 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); uint16_t idx = xyMap.mapToIndex(x, y);
if (idx < n) { if (idx < n) {
@ -229,8 +227,8 @@ void bilinearExpandArbitraryFloat(const CRGB *input, CRGB *output,
} }
// Floating-point version for power-of-two grid sizes // Floating-point version for power-of-two grid sizes
void bilinearExpandFloat(const CRGB *input, CRGB *output, uint8_t inputWidth, void upscaleFloat(const CRGB *input, CRGB *output, uint8_t inputWidth,
uint8_t inputHeight, XYMap xyMap) { uint8_t inputHeight, XYMap xyMap) {
uint8_t outputWidth = xyMap.getWidth(); uint8_t outputWidth = xyMap.getWidth();
uint8_t outputHeight = xyMap.getHeight(); uint8_t outputHeight = xyMap.getHeight();
if (outputWidth != xyMap.getWidth() || 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; CRGB result;
result.r = 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 = 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 = 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); uint16_t idx = xyMap.mapToIndex(x, y);
if (idx < n) { if (idx < n) {

View 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

View file

@ -237,7 +237,7 @@ XYPathPtr XYPath::NewCatmullRomPath(uint16_t width, uint16_t height,
} }
XYPathPtr XYPath::NewCustomPath(const fl::function<vec2f(float)> &f, XYPathPtr XYPath::NewCustomPath(const fl::function<vec2f(float)> &f,
const rect<int> &drawbounds, const rect<int16_t> &drawbounds,
const TransformFloat &transform, const TransformFloat &transform,
const char *name) { const char *name) {
@ -250,7 +250,7 @@ XYPathPtr XYPath::NewCustomPath(const fl::function<vec2f(float)> &f,
if (!transform.is_identity()) { if (!transform.is_identity()) {
out->setTransform(transform); out->setTransform(transform);
} }
rect<int> bounds; rect<int16_t> bounds;
if (path->hasDrawBounds(&bounds)) { if (path->hasDrawBounds(&bounds)) {
if (!bounds.mMin.is_zero()) { if (!bounds.mMin.is_zero()) {
// Set the bounds to the path's bounds // Set the bounds to the path's bounds

View file

@ -18,8 +18,6 @@
#include "fl/transform.h" #include "fl/transform.h"
#include "fl/xypath_impls.h" #include "fl/xypath_impls.h"
#include "fl/avr_disallowed.h"
namespace fl { namespace fl {
class Gradient; class Gradient;
@ -37,7 +35,6 @@ namespace xypath_detail {
fl::Str unique_missing_name(const char *prefix = "XYCustomPath: "); fl::Str unique_missing_name(const char *prefix = "XYCustomPath: ");
} // namespace xypath_detail } // namespace xypath_detail
AVR_DISALLOWED
class XYPath : public Referent { class XYPath : public Referent {
public: public:
///////////////////////////////////////////// /////////////////////////////////////////////
@ -57,7 +54,7 @@ class XYPath : public Referent {
// Custom path using just a function. // Custom path using just a function.
static XYPathPtr static XYPathPtr
NewCustomPath(const fl::function<vec2f(float)> &path, 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 TransformFloat &transform = TransformFloat(),
const char *name = nullptr); const char *name = nullptr);
@ -148,10 +145,10 @@ class XYPathFunction : public XYPathGenerator {
const Str name() const override { return mName; } const Str name() const override { return mName; }
void setName(const Str &name) { mName = name; } void setName(const Str &name) { mName = name; }
fl::rect<int> drawBounds() const { return mDrawBounds; } fl::rect<int16_t> drawBounds() const { return mDrawBounds; }
void setDrawBounds(const fl::rect<int> &bounds) { mDrawBounds = bounds; } 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) { if (bounds) {
*bounds = mDrawBounds; *bounds = mDrawBounds;
} }
@ -161,7 +158,7 @@ class XYPathFunction : public XYPathGenerator {
private: private:
fl::function<vec2f(float)> mFunction; fl::function<vec2f(float)> mFunction;
fl::Str mName = "XYPathFunction Unnamed"; fl::Str mName = "XYPathFunction Unnamed";
fl::rect<int> mDrawBounds; fl::rect<int16_t> mDrawBounds;
}; };
} // namespace fl } // namespace fl

View file

@ -57,7 +57,7 @@ class XYPathGenerator : public Referent {
virtual const Str name() const = 0; virtual const Str name() const = 0;
virtual vec2f compute(float alpha) = 0; virtual vec2f compute(float alpha) = 0;
// No writes when returning false. // No writes when returning false.
virtual bool hasDrawBounds(rect<int> *bounds) { virtual bool hasDrawBounds(rect<int16_t> *bounds) {
FASTLED_UNUSED(bounds); FASTLED_UNUSED(bounds);
return false; return false;
} }

View file

@ -14,6 +14,8 @@
#include "fl/scoped_ptr.h" #include "fl/scoped_ptr.h"
#include "fl/xymap.h" #include "fl/xymap.h"
#include "fx/fx2d.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 #define ANIMARTRIX_INTERNAL
#include "animartrix_detail.hpp" #include "animartrix_detail.hpp"
@ -93,6 +95,8 @@ class Animartrix : public Fx2d {
int fxGet() const { return static_cast<int>(current_animation); } int fxGet() const { return static_cast<int>(current_animation); }
Str fxName() const override { return "Animartrix:"; } Str fxName() const override { return "Animartrix:"; }
void fxNext(int fx = 1) { fxSet(fxGet() + fx); } void fxNext(int fx = 1) { fxSet(fxGet() + fx); }
void setColorOrder(EOrder order) { color_order = order; }
EOrder getColorOrder() const { return color_order; }
private: private:
friend void AnimartrixLoop(Animartrix &self, uint32_t now); friend void AnimartrixLoop(Animartrix &self, uint32_t now);
@ -102,6 +106,7 @@ class Animartrix : public Fx2d {
fl::scoped_ptr<FastLEDANIMartRIX> impl; fl::scoped_ptr<FastLEDANIMartRIX> impl;
CRGB *leds = nullptr; // Only set during draw, then unset back to nullptr. CRGB *leds = nullptr; // Only set during draw, then unset back to nullptr.
AnimartrixAnim current_animation = RGB_BLOBS5; AnimartrixAnim current_animation = RGB_BLOBS5;
EOrder color_order = RGB;
}; };
void AnimartrixLoop(Animartrix &self, uint32_t now); void AnimartrixLoop(Animartrix &self, uint32_t now);
@ -263,6 +268,17 @@ const char *Animartrix::getAnimationName(AnimartrixAnim animation) {
void Animartrix::draw(DrawContext ctx) { void Animartrix::draw(DrawContext ctx) {
this->leds = ctx.leds; this->leds = ctx.leds;
AnimartrixLoop(*this, ctx.now); 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; this->leds = nullptr;
} }

View file

@ -3,7 +3,7 @@
#define FASTLED_INTERNAL #define FASTLED_INTERNAL
#include "FastLED.h" #include "FastLED.h"
#include "fl/bilinear_expansion.h" #include "fl/upscale.h"
#include "fl/ptr.h" #include "fl/ptr.h"
#include "fl/xymap.h" #include "fl/xymap.h"
#include "fx/fx2d.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, void ScaleUp::expand(const CRGB *input, CRGB *output, uint16_t width,
uint16_t height, XYMap mXyMap) { uint16_t height, XYMap mXyMap) {
#if FASTLED_SCALE_UP == FASTLED_SCALE_UP_ALWAYS_POWER_OF_2 #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 #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 #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 #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 #else
#error "Invalid FASTLED_SCALE_UP" #error "Invalid FASTLED_SCALE_UP"
#endif #endif

View file

@ -9,7 +9,7 @@
#include <stdint.h> #include <stdint.h>
#include "fl/bilinear_expansion.h" #include "fl/upscale.h"
#include "fl/ptr.h" #include "fl/ptr.h"
#include "fl/vector.h" #include "fl/vector.h"
#include "fl/xymap.h" #include "fl/xymap.h"

View file

@ -40,6 +40,8 @@ typedef fl::FixedVector<int, 16> PinList16;
typedef uint8_t Pin; typedef uint8_t Pin;
bool gPsramInited = false;
// Maps multiple pins and CRGB strips to a single I2S_Esp32 object. // Maps multiple pins and CRGB strips to a single I2S_Esp32 object.
@ -180,6 +182,13 @@ class Driver: public InternalI2SDriver {
}; };
InternalI2SDriver* InternalI2SDriver::create() { InternalI2SDriver* InternalI2SDriver::create() {
if (!gPsramInited) {
gPsramInited = true;
bool ok = psramInit();
if (!ok) {
log_e("PSRAM initialization failed, I2S driver may crash.");
}
}
return new Driver(); return new Driver();
} }

View file

@ -14,6 +14,20 @@
#include "fl/vector.h" #include "fl/vector.h"
#include "eorder.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 { namespace fl {

View file

@ -118,6 +118,11 @@ public:
// GPIO 20-22, 24-26 used by default for SPI flash. // 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)) #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 #elif CONFIG_IDF_TARGET_ESP32H2
// 22 GPIO pins. ESPIDF defines all pins as valid. // 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. // 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) #define FASTLED_UNUSABLE_PIN_MASK (0ULL)
#endif #endif
#endif #endif

View file

@ -25,11 +25,7 @@ private:
static RmtController5::DmaMode DefaultDmaMode() static RmtController5::DmaMode DefaultDmaMode()
{ {
#ifdef FASTLED_RMT_USE_DMA
return RmtController5::DMA_ENABLED;
#else
return RmtController5::DMA_AUTO; return RmtController5::DMA_AUTO;
#endif
} }
public: public:

View file

@ -1,9 +1,16 @@
#pragma once #pragma once
#if !defined(PROGMEM)
#define 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_BYTE_NEAR(x) (*((const uint8_t *)(x)))
#define FL_PGM_READ_WORD_NEAR(x) (*((const uint16_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_PGM_READ_DWORD_NEAR(x) (*((const uint32_t *)(x)))
#define FL_ALIGN_PROGMEM #define FL_ALIGN_PROGMEM
#define FL_PROGMEM_USES_NULL 1

View file

@ -11,7 +11,7 @@
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_PUSH
FL_DISABLE_WARNING(global-constructors) FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
static const auto start_time = std::chrono::system_clock::now(); static const auto start_time = std::chrono::system_clock::now();

View file

@ -42,7 +42,7 @@ class ActiveStripData : public fl::EngineEvents::Listener {
updateScreenMap(id, screenmap); updateScreenMap(id, screenmap);
} }
const bool hasScreenMap(int id) const { return mScreenMap.has(id); } bool hasScreenMap(int id) const { return mScreenMap.has(id); }
private: private:
friend class fl::Singleton<ActiveStripData>; friend class fl::Singleton<ActiveStripData>;

View file

@ -45,7 +45,7 @@ class ActiveStripData2 : public fl::EngineEvents::Listener {
updateScreenMap(id, screenmap); updateScreenMap(id, screenmap);
} }
const bool hasScreenMap(int id) const { return mScreenMap.has(id); } bool hasScreenMap(int id) const { return mScreenMap.has(id); }
private: private:
friend class fl::Singleton<ActiveStripData2>; friend class fl::Singleton<ActiveStripData2>;

View file

@ -17,6 +17,19 @@
#include "fl/hash_set.h" #include "fl/hash_set.h"
#include "fl/vector.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; using namespace fl;
@ -80,9 +93,9 @@ namespace doctest {
} }
}; };
template<typename T> template<typename T, typename Alloc>
struct StringMaker<fl::vector<T>> { struct StringMaker<fl::vector<T, Alloc>> {
static String convert(const fl::vector<T>& value) { static String convert(const fl::vector<T, Alloc>& value) {
fl::Str out; fl::Str out;
out.append(value); out.append(value);
return out.c_str(); return out.c_str();

View file

@ -2,138 +2,119 @@
#include "fl/math_macros.h" #include "fl/math_macros.h"
#include "test.h" #include "test.h"
#include "fl/algorithm.h"
#include "fl/sstream.h" #include "fl/sstream.h"
#include "fl/corkscrew.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 NUM_LEDS 288
#define TWO_PI (PI * 2.0) #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; using namespace fl;
TEST_CASE("Corkscrew generateMap") { TEST_CASE("Corkscrew Circle10 test") {
Corkscrew::Input input; Corkscrew::Input input;
input.totalHeight = 10.0f; input.totalLength = 10.0f; // Total length of the corkscrew in centimeters
input.totalAngle = TWO_PI; input.totalHeight = 0.0f;
input.offsetCircumference = 0.0f; input.totalTurns = 1.0f;
input.numLeds = 10; input.offsetCircumference = 0.0f; // No offset
input.numLeds = 10; // Default to dense 144 LEDs times two strips
Corkscrew::Output output = Corkscrew::generateMap(input); Corkscrew::State output = Corkscrew::generateState(input);
fl::vector<vec2f> expected_values;
CHECK_EQ(output.width, 10); expected_values.push_back(vec2f(0.0f, 0.0f)); // First LED at the bottom
CHECK_EQ(output.height, 1); // One vertical segment for one turn expected_values.push_back(vec2f(1.0f, 0.0f)); // Second LED in the middle
CHECK_EQ(output.mapping.size(), 10); // 10 LEDs around the corkscrew 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
CHECK_GE(output.mapping[0].x, 0.0f); expected_values.push_back(vec2f(4.0f, 0.0f)); // Fifth LED at the top
CHECK_LE(output.mapping[0].x, 10.0f); expected_values.push_back(vec2f(5.0f, 0.0f)); // Sixth LED at the top
CHECK_GE(output.mapping[0].y, 0.0f); expected_values.push_back(vec2f(6.0f, 0.0f)); // Seventh LED at the top
CHECK_LE(output.mapping[0].y, 1.0f); // 1 vertical segment for 2π angle 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
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");
REQUIRE_EQ(output.width, 10);
REQUIRE_EQ(output.height, 1);
} }
TEST_CASE("Corkscrew generateMap with two turns") { TEST_CASE("Tile2x2_u8_wrap wrap-around test with width and height") {
Corkscrew::Input input; // Initialize a Tile2x2_u8 with known values and set origin beyond boundaries
input.totalHeight = 10.0f; Tile2x2_u8 originalTile;
input.totalAngle = 2 * TWO_PI; // Two full turns originalTile.setOrigin(3, 3); // Set the origin beyond the width and height
input.numLeds = 10; // 10 LEDs around the corkscrew originalTile.at(0, 0) = 1;
input.offsetCircumference = 0.0f; 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); // Verify that the conversion wraps around correctly
CHECK_EQ(output.height, 2); // Two vertical segments for two turns REQUIRE_EQ(cycTile.at(0, 0).first.x, 1); // Wraps around to (1, 1)
CHECK_EQ(output.mapping.size(), 10); // 5 width * 2 height 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) // Verify that the values are correct
CHECK_GE(output.mapping[0].x, 0.0f); REQUIRE_EQ(cycTile.at(0, 0).second, 1);
CHECK_LE(output.mapping[0].x, 5.0f); REQUIRE_EQ(cycTile.at(0, 1).second, 2);
CHECK_GE(output.mapping[0].y, 0.0f); REQUIRE_EQ(cycTile.at(1, 0).second, 3);
CHECK_LE(output.mapping[0].y, 2.0f); // 2 vertical segments for 4π angle REQUIRE_EQ(cycTile.at(1, 1).second, 4);
} }
TEST_CASE("Corkscrew circumference test") { TEST_CASE("Tile2x2_u8_wrap conversion with width and height") {
Corkscrew::Input input; // Initialize a Tile2x2_u8 with known values
// Use defaults: totalHeight = 100, totalAngle = 19 * 2 * PI Tile2x2_u8 originalTile;
input.totalHeight = 23.25f; // Total height of the corkscrew in centimeters originalTile.setOrigin(0, 0); // Set the origin to (0, 0)
input.totalAngle = 19.0f * TWO_PI; // Default to 19 turns originalTile.at(0, 0) = 1;
input.offsetCircumference = 0.0f; // No offset originalTile.at(0, 1) = 2;
input.numLeds = 288; // Default to dense 144 LEDs times two strips 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 // Verify that the conversion is correct
CHECK_EQ(output.width, 16); REQUIRE_EQ(cycTile.at(0, 0).second, 1);
CHECK_EQ(output.height, 19); REQUIRE_EQ(cycTile.at(0, 1).second, 2);
CHECK_EQ(output.mapping.size(), 288); REQUIRE_EQ(cycTile.at(1, 0).second, 3);
REQUIRE_EQ(cycTile.at(1, 1).second, 4);
// Check that circumference matches calculated value }
// float expectedCircumference = 100.0f / 19.0f;
// CHECK_CLOSE(output.circumference, expectedCircumference, 0.01f); 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)
} }

View file

@ -14,7 +14,7 @@
"m5stack/M5Utility": "*", "m5stack/M5Utility": "*",
"m5stack/M5HAL": "*" "m5stack/M5HAL": "*"
}, },
"version": "0.1.4", "version": "0.1.5",
"frameworks": [ "frameworks": [
"arduino" "arduino"
], ],

View file

@ -1,5 +1,5 @@
name=M5UnitUnified name=M5UnitUnified
version=0.1.4 version=0.1.5
author=M5Stack author=M5Stack
maintainer=M5Stack maintainer=M5Stack
sentence=M5UnitUnified is a library for unified handling of various M5 units products. (Alpha version) sentence=M5UnitUnified is a library for unified handling of various M5 units products. (Alpha version)

View file

@ -55,7 +55,7 @@ public:
/*! /*!
@class ComponentTestBase @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 U m5::unit::Component-derived classes to be tested
@tparam TP parameter type for testing. see also INSTANTIATE_TEST_SUITE_P @tparam TP parameter type for testing. see also INSTANTIATE_TEST_SUITE_P
*/ */
@ -109,6 +109,67 @@ protected:
m5::unit::UnitUnified Units; 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 googletest
} // namespace unit } // namespace unit
} // namespace m5 } // namespace m5

View file

@ -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, 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; duration = 0;
auto start = esp_timer_get_time(); auto start = esp_timer_get_time();

View file

@ -87,7 +87,7 @@ public:
} }
inline virtual m5::hal::error::error_t pulseInRX(uint32_t& duration, const int state, 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); return pulse_in(duration, rx_pin(), state, timeout_us);
} }
@ -114,7 +114,7 @@ public:
return read_analog(v, tx_pin()); return read_analog(v, tx_pin());
} }
inline virtual m5::hal::error::error_t pulseInTX(uint32_t& duration, const int state, 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); return pulse_in(duration, tx_pin(), state, timeout_us);
} }

View file

@ -38,6 +38,13 @@ bool declrare_use_rmt_channel(const int ch)
return false; 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 to_rmt_config_tx(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
{ {
rmt_config_t out{}; 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) 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 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; 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; return m5::hal::error::error_t::UNKNOWN_ERROR;
} }

View file

@ -48,6 +48,17 @@ public:
GPIOImplV2(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase::GPIOImpl(rx_pin, tx_pin) 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) bool begin(const gpio::adapter_config_t& cfg)
{ {
@ -88,6 +99,9 @@ public:
rmt_transmit_config_t tx_config = {}; 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), auto err = rmt_transmit(_tx_handle, copy_encoder, (gpio::m5_rmt_item_t*)data, len * sizeof(gpio::m5_rmt_item_t),
&tx_config); &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) { if (err == ESP_OK && waitMs) {
err = rmt_tx_wait_all_done(_tx_handle, waitMs); err = rmt_tx_wait_all_done(_tx_handle, waitMs);
if (err != ESP_OK) { if (err != ESP_OK) {

View file

@ -11,7 +11,7 @@
"url": "https://github.com/m5stack/M5Utility.git" "url": "https://github.com/m5stack/M5Utility.git"
}, },
"dependencies": [], "dependencies": [],
"version": "0.0.2", "version": "0.0.3",
"frameworks": [ "frameworks": [
"arduino" "arduino"
], ],

View file

@ -1,5 +1,5 @@
name=M5Utility name=M5Utility
version=0.0.2 version=0.0.3
author=M5Stack author=M5Stack
maintainer=M5Stack maintainer=M5Stack
sentence=Library for other M5 libraries and products sentence=Library for other M5 libraries and products

View file

@ -81,7 +81,7 @@ extends = m5base
board = m5stack-nanoc6 board = m5stack-nanoc6
platform = https://github.com/platformio/platform-espressif32.git platform = https://github.com/platformio/platform-espressif32.git
platform_packages = 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 platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1
board_build.partitions = default.csv board_build.partitions = default.csv
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}

View file

@ -30,6 +30,7 @@
#include "m5_utility/string.hpp" #include "m5_utility/string.hpp"
#include "m5_utility/conversion.hpp" #include "m5_utility/conversion.hpp"
#include "m5_utility/math.hpp" #include "m5_utility/math.hpp"
#include "m5_utility/button_status.hpp"
#include "m5_utility/misc.hpp" #include "m5_utility/misc.hpp"
/*! /*!

View 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

View 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

View file

@ -1,5 +1,5 @@
/* /*
* Spdx-FileCopyrightText: 2024 M5Stack Technology CO LTD * SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
* *
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */

Some files were not shown because too many files have changed in this diff Show more