WIP: feature/esp32-ng-basic #1
294
.gitignore
vendored
Normal file
294
.gitignore
vendored
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
# ---> esp-idf
|
||||||
|
# gitignore template for esp-idf, the official development framework for ESP32
|
||||||
|
# https://github.com/espressif/esp-idf
|
||||||
|
|
||||||
|
build/
|
||||||
|
sdkconfig.old
|
||||||
|
|
||||||
|
# ---> VisualStudioCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
# ---> Python
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
||||||
|
# ---> esp-idf
|
||||||
|
# gitignore template for esp-idf, the official development framework for ESP32
|
||||||
|
# https://github.com/espressif/esp-idf
|
||||||
|
|
||||||
|
build/
|
||||||
|
sdkconfig.old
|
||||||
|
|
||||||
|
# ---> CMake
|
||||||
|
CMakeLists.txt.user
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles
|
||||||
|
CMakeScripts
|
||||||
|
Testing
|
||||||
|
Makefile
|
||||||
|
cmake_install.cmake
|
||||||
|
install_manifest.txt
|
||||||
|
compile_commands.json
|
||||||
|
CTestTestfile.cmake
|
||||||
|
_deps
|
||||||
|
|
||||||
|
# ---> C
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Linker output
|
||||||
|
*.ilk
|
||||||
|
*.map
|
||||||
|
*.exp
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
|
# Debug files
|
||||||
|
*.dSYM/
|
||||||
|
*.su
|
||||||
|
*.idb
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Kernel Module Compile Results
|
||||||
|
*.mod*
|
||||||
|
*.cmd
|
||||||
|
.tmp_versions/
|
||||||
|
modules.order
|
||||||
|
Module.symvers
|
||||||
|
Mkfile.old
|
||||||
|
dkms.conf
|
||||||
|
|
||||||
|
# ---> C++
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
|
||||||
|
.vscode/settings.json
|
||||||
|
sdkconfig.defaults
|
||||||
|
.clangd
|
||||||
5
CMakeLists.txt
Normal file
5
CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# ESP32 LED Controller Firmware
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(led_controller)
|
||||||
144
README.md
144
README.md
@ -1,38 +1,124 @@
|
|||||||
# WS2812B-LED-RC-Controller
|
# ESP32 LED Controller for Model Aircraft
|
||||||
WS2812B Controller for RC plane night flying with spotlight
|
|
||||||
|
|
||||||
#### Fast overview: Video will come soon.
|
Professional LED controller firmware for ESP32. Designed for model aircraft with WS2812B LED strips.
|
||||||
|
|
||||||
[]
|
## Features
|
||||||
(https://www.youtube.com/watch?v=)
|
|
||||||
|
|
||||||
## 1.Hardware:
|
### Hardware Support
|
||||||
- Atmel Atmega328p
|
- **ESP32 DevKitC** and **ESP32-C3 MINI** Development Board
|
||||||
- WS2812B stirp (got mine from https://www.banggood.com/5M-45W-150SMD-WS2812B-LED-RGB-Colorful-Strip-Light-Waterproof-IP65-WhiteBlack-PCB-DC5V-p-1035640.html)
|
- Dual WS2812B LED strip support (configurable GPIOs)
|
||||||
- 3W 12V LED
|
- PWM signal input for RC control
|
||||||
- Transistor 2N3904
|
- Real-time LED animation at 60 FPS
|
||||||
- Capacitors - 10uF/25V
|
|
||||||
- Socket for Atmega
|
|
||||||
- Some servo wires
|
|
||||||
- Crystal 16MHz
|
|
||||||
- Capacitor Ceramic 22pF
|
|
||||||
- PCB of your choice
|
|
||||||
|
|
||||||
## 2.Software:
|
### Animation Modes
|
||||||
- get your isp-programmer (ex. USBasp) working, linux is your friend
|
1. **Black** - All LEDs off
|
||||||
- install latest Arduino IDE and drivers
|
2. **Red** - Solid red
|
||||||
- install FastLED https://github.com/FastLED/FastLED
|
3. **Blue** - Solid blue
|
||||||
|
4. **Green** - Solid green
|
||||||
|
5. **White** - Solid white
|
||||||
|
6. **Rainbow** - Smooth rainbow gradient
|
||||||
|
7. **Rainbow with Glitter** - Rainbow with sparkles
|
||||||
|
8. **Confetti** - Random colored speckles
|
||||||
|
9. **Sinelon** - Sweeping colored dot with trails
|
||||||
|
10. **BPM** - Pulsing stripes at 33 BPM
|
||||||
|
11. **Navigation** - Aviation lights (red left, green right)
|
||||||
|
12. **Chase (Red)** - Red dot chase effect
|
||||||
|
13. **Chase (RGB)** - RGB cycling chase effect
|
||||||
|
14. **Random** - Random LED colors
|
||||||
|
|
||||||
## 3.Libraries used in this project:
|
## Project Structure
|
||||||
- FastLED from https://github.com/FastLED/FastLED
|
|
||||||
|
|
||||||
### Installation:
|
```
|
||||||
1. prepare Hardware. Ground to Ground and the rest like the schematics (comming soon).
|
led-controller-firmware/
|
||||||
2. Upload the sketch to the Arduino with the ISP-Programmer.
|
├── main/
|
||||||
3. Set the switches on your RC control for the two channels.
|
│ ├── main.c # Application entry point
|
||||||
7. Power everything up.
|
│ ├── control.c/h # initialization
|
||||||
8. Enjoy your WS2812B-LED-RC-Controller
|
│ ├── config.c/h # NVS
|
||||||
|
│ ├── led.c/h # WS2812B control (RMT driver)
|
||||||
|
│ ├── rcsignal.c/h # PWM signal reading
|
||||||
|
│ ├── localbtn.c/h # Local btn reading
|
||||||
|
│ └── animation.c/h # LED animation patterns
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── sdkconfig.defaults
|
||||||
|
└── partitions.csv # OTA-enabled partition table
|
||||||
|
```
|
||||||
|
|
||||||
Bug and Features: Please report bugs and wishes to me. Thanks!
|
## Build Instructions
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
1. Install ESP-IDF v5.0 or later
|
||||||
|
|
||||||
|
2. For ESP32-C3, ensure RISC-V toolchain is installed
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd led-controller-firmware
|
||||||
|
|
||||||
|
# For ESP32 DevKitC
|
||||||
|
idf.py set-target esp32
|
||||||
|
idf.py build
|
||||||
|
|
||||||
|
# For ESP32-C3 MINI
|
||||||
|
idf.py set-target esp32c3
|
||||||
|
idf.py build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flashing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
idf.py -p /dev/ttyUSB0 flash monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `/dev/ttyUSB0` with your serial port.
|
||||||
|
|
||||||
|
## Hardware Setup
|
||||||
|
|
||||||
|
### Wiring
|
||||||
|
```
|
||||||
|
ESP32 Pin -> Component
|
||||||
|
----------- ----------
|
||||||
|
GPIO XX -> WS2812B Strip A Data
|
||||||
|
GPIO XX -> WS2812B Strip B Data
|
||||||
|
GPIO XX -> RC PWM Signal
|
||||||
|
GPIO XX -> Local button Signal
|
||||||
|
GND -> Common Ground
|
||||||
|
5V -> LED Strip Power (if current < 500mA)
|
||||||
|
```
|
||||||
|
|
||||||
|
### LED Strips
|
||||||
|
- **WS2812B** strips require 5V power
|
||||||
|
- Each LED draws ~60mA at full white
|
||||||
|
- Use external power supply for >10 LEDs
|
||||||
|
- Add 100-500µF capacitor near strips
|
||||||
|
- Add 330Ω resistor on data line
|
||||||
|
|
||||||
|
### PWM Signal
|
||||||
|
- Standard RC PWM: 1000-2000µs pulse width
|
||||||
|
- 1500µs threshold for mode switching
|
||||||
|
- Rising edge >1500µs after <1500µs triggers next mode
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Adding New Animations
|
||||||
|
|
||||||
|
1. Add mode to `animation_mode_t` enum in `animation.h`
|
||||||
|
2. Implement animation function in `animation.c`
|
||||||
|
3. Add case to `animation_update()` switch statement
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and flash
|
||||||
|
idf.py build flash
|
||||||
|
|
||||||
|
# Monitor output
|
||||||
|
idf.py monitor
|
||||||
|
|
||||||
|
# Exit monitor: Ctrl+]
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE)
|
||||||
|
|
||||||
|
|||||||
212
functions.ino
212
functions.ino
@ -1,212 +0,0 @@
|
|||||||
boolean getRC01() {
|
|
||||||
rc01Val = pulseIn(rc01, HIGH);
|
|
||||||
//Serial.println(rc01Val);
|
|
||||||
if (rc01Val > 1500) {
|
|
||||||
//Serial.println("RC1 ON");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
//Serial.println("RC1 OFF");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean getRC02() {
|
|
||||||
rc02Val = pulseIn(rc02, HIGH);
|
|
||||||
// Serial.println(rc02Val);
|
|
||||||
if (rc02Val < 1500) {
|
|
||||||
pullRC = true;
|
|
||||||
}
|
|
||||||
if (rc02Val > 1500 && pullRC) {
|
|
||||||
//Serial.println("RC2 ON");
|
|
||||||
pullRC = false;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
//Serial.println("RC2 OFF");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void serialPrintModus(int modus) {
|
|
||||||
switch (modus) {
|
|
||||||
case 0:
|
|
||||||
Serial.println("Black");
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
Serial.println("Red");
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
Serial.println("Blue");
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
Serial.println("Green");
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
Serial.println("White");
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
Serial.println("Rainbow");
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
Serial.println("RainbowWithGlitter");
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
Serial.println("Confetti");
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
Serial.println("Sinelon");
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
Serial.println("BPM");
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
Serial.println("Navigation");
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
Serial.println("Chase");
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
Serial.println("ChaseRGB");
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
Serial.println("Random");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void rainbow()
|
|
||||||
{
|
|
||||||
Serial.println("Rainbow");
|
|
||||||
// FastLED's built-in rainbow generator
|
|
||||||
fill_rainbow( leds, NUM_LEDS, gHue, 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rainbowWithGlitter()
|
|
||||||
{
|
|
||||||
// built-in FastLED rainbow, plus some random sparkly glitter
|
|
||||||
rainbow();
|
|
||||||
addGlitter(255);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addGlitter( fract8 chanceOfGlitter)
|
|
||||||
{
|
|
||||||
if ( random8() < chanceOfGlitter) {
|
|
||||||
leds[ random16(NUM_LEDS) ] += CRGB::White;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void confetti()
|
|
||||||
{
|
|
||||||
// random colored speckles that blink in and fade smoothly
|
|
||||||
fadeToBlackBy( leds, NUM_LEDS, 10);
|
|
||||||
int pos = random16(NUM_LEDS);
|
|
||||||
leds[pos] += CHSV( gHue + random8(64), 200, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sinelon()
|
|
||||||
{
|
|
||||||
// a colored dot sweeping back and forth, with fading trails
|
|
||||||
fadeToBlackBy( leds, NUM_LEDS, 20);
|
|
||||||
int pos = beatsin16(13, 0, NUM_LEDS);
|
|
||||||
leds[pos] += CHSV( gHue, 255, 192);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bpm()
|
|
||||||
{
|
|
||||||
// colored stripes pulsing at a defined Beats-Per-Minute (BPM)
|
|
||||||
uint8_t BeatsPerMinute = 33;
|
|
||||||
CRGBPalette16 palette = PartyColors_p;
|
|
||||||
uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);
|
|
||||||
for ( int i = 0; i < NUM_LEDS; i++) { //9948
|
|
||||||
leds[i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void blackMode() {
|
|
||||||
fill_solid(leds, NUM_LEDS, CRGB::Black); // Just to be sure, let's really make it BLACK.
|
|
||||||
}
|
|
||||||
|
|
||||||
void redMode() {
|
|
||||||
fill_solid(leds, NUM_LEDS, CRGB::Red);
|
|
||||||
}
|
|
||||||
|
|
||||||
void blueMode() {
|
|
||||||
fill_solid(leds, NUM_LEDS, CRGB::Blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
void greenMode() {
|
|
||||||
fill_solid(leds, NUM_LEDS, CRGB::Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
void whiteMode() {
|
|
||||||
fill_solid(leds, NUM_LEDS, CRGB::White);
|
|
||||||
}
|
|
||||||
|
|
||||||
void navigation() {
|
|
||||||
FastLED.clear();
|
|
||||||
leds[0] = CRGB::Red;
|
|
||||||
leds[1] = CRGB::Red;
|
|
||||||
leds[2] = CRGB::Red;
|
|
||||||
leds[41] = CRGB::Green;
|
|
||||||
leds[42] = CRGB::Green;
|
|
||||||
leds[43] = CRGB::Green;
|
|
||||||
|
|
||||||
leds[5] = CRGB::White;
|
|
||||||
leds[6] = CRGB::White;
|
|
||||||
leds[37] = CRGB::White;
|
|
||||||
leds[38] = CRGB::White;
|
|
||||||
|
|
||||||
FastLED.delay(100);
|
|
||||||
|
|
||||||
leds[5] = CRGB::Black;
|
|
||||||
leds[6] = CRGB::Black;
|
|
||||||
leds[37] = CRGB::Black;
|
|
||||||
leds[38] = CRGB::Black;
|
|
||||||
}
|
|
||||||
|
|
||||||
void chase() {
|
|
||||||
FastLED.clear();
|
|
||||||
// a colored dot sweeping back and forth, with fading trails
|
|
||||||
//fadeToBlackBy( leds, NUM_LEDS, 20);
|
|
||||||
int pos = beatsin16(40, 0, NUM_LEDS);
|
|
||||||
leds[pos] = CRGB::Red;
|
|
||||||
if (pos < 41) {
|
|
||||||
leds[pos + 1] = CRGB::Red;
|
|
||||||
leds[pos + 2] = CRGB::Red;
|
|
||||||
}
|
|
||||||
if (pos > 1) {
|
|
||||||
leds[pos - 1] = CRGB::Red;
|
|
||||||
leds[pos - 2] = CRGB::Red;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void chaseRGB() {
|
|
||||||
FastLED.clear();
|
|
||||||
// a colored dot sweeping back and forth, with fading trails
|
|
||||||
//fadeToBlackBy( leds, NUM_LEDS, 20);
|
|
||||||
int pos = beatsin16(40, 0, NUM_LEDS);
|
|
||||||
leds[pos] += CHSV( gHue, 255, 192);
|
|
||||||
if (pos < 41) {
|
|
||||||
leds[pos + 1] += CHSV( gHue, 255, 192);
|
|
||||||
leds[pos + 2] += CHSV( gHue, 255, 192);
|
|
||||||
}
|
|
||||||
if (pos > 1) {
|
|
||||||
leds[pos - 1] += CHSV( gHue, 255, 192);
|
|
||||||
leds[pos - 2] += CHSV( gHue, 255, 192);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void randomMode(){
|
|
||||||
randomVal = random(0,45);
|
|
||||||
|
|
||||||
if(randomVal == 44){
|
|
||||||
if(random(5,11) == 9){
|
|
||||||
FastLED.clear();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
leds[randomVal] = random(0, 16777216);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
11
main/CMakeLists.txt
Normal file
11
main/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS
|
||||||
|
"main.c"
|
||||||
|
"control.c"
|
||||||
|
"led.c"
|
||||||
|
"rcsignal.c"
|
||||||
|
"animation.c"
|
||||||
|
"localbtn.c"
|
||||||
|
"config.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
)
|
||||||
468
main/animation.c
Normal file
468
main/animation.c
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
/**
|
||||||
|
* @file animation.c
|
||||||
|
* @brief LED animation patterns implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "animation.h"
|
||||||
|
#include "led.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_random.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
static const char *TAG = "ANIMATION";
|
||||||
|
|
||||||
|
#define FRAMES_PER_SECOND 60
|
||||||
|
|
||||||
|
static animation_mode_t current_mode = ANIM_BLACK;
|
||||||
|
static uint8_t global_hue = 0;
|
||||||
|
static uint32_t frame_counter = 0;
|
||||||
|
|
||||||
|
// Beat calculation helper
|
||||||
|
static int16_t beatsin16(uint8_t bpm, int16_t min_val, int16_t max_val)
|
||||||
|
{
|
||||||
|
// Use uint64_t to prevent overflow
|
||||||
|
uint64_t us = esp_timer_get_time(); // Microseconds
|
||||||
|
|
||||||
|
// Calculate beat phase (0-65535 repeating at BPM rate)
|
||||||
|
// beats_per_minute → beats_per_microsecond = bpm / 60,000,000
|
||||||
|
uint64_t beat_phase = (us * (uint64_t)bpm * 65536ULL) / 60000000ULL;
|
||||||
|
uint16_t beat16 = (uint16_t)(beat_phase & 0xFFFF);
|
||||||
|
|
||||||
|
// Convert to angle (0 to 2π)
|
||||||
|
float angle = (beat16 / 65535.0f) * 2.0f * M_PI;
|
||||||
|
float sin_val = sinf(angle);
|
||||||
|
|
||||||
|
// Map sin (-1 to +1) to output range (min_val to max_val)
|
||||||
|
int16_t range = max_val - min_val;
|
||||||
|
int16_t result = min_val + (int16_t)((sin_val + 1.0f) * range / 2.0f);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random helper
|
||||||
|
static uint8_t random8(void)
|
||||||
|
{
|
||||||
|
return esp_random() & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t random16(uint16_t max)
|
||||||
|
{
|
||||||
|
if (max == 0)
|
||||||
|
return 0;
|
||||||
|
return esp_random() % max;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation implementations
|
||||||
|
static void anim_black(void)
|
||||||
|
{
|
||||||
|
rgb_t black = {0, 0, 0};
|
||||||
|
led_fill_a(black);
|
||||||
|
led_fill_b(black);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_red(void)
|
||||||
|
{
|
||||||
|
rgb_t red = {255, 0, 0};
|
||||||
|
led_fill_a(red);
|
||||||
|
led_fill_b(red);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_blue(void)
|
||||||
|
{
|
||||||
|
rgb_t blue = {0, 0, 255};
|
||||||
|
led_fill_a(blue);
|
||||||
|
led_fill_b(blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_green(void)
|
||||||
|
{
|
||||||
|
rgb_t green = {0, 255, 0};
|
||||||
|
led_fill_a(green);
|
||||||
|
led_fill_b(green);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_white(void)
|
||||||
|
{
|
||||||
|
rgb_t white = {255, 255, 255};
|
||||||
|
led_fill_a(white);
|
||||||
|
led_fill_b(white);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_rainbow(void)
|
||||||
|
{
|
||||||
|
// Rainbow generator
|
||||||
|
uint16_t num_leds_a = led_get_num_leds_a();
|
||||||
|
uint16_t num_leds_b = led_get_num_leds_b();
|
||||||
|
uint16_t num_leds = num_leds_a + num_leds_b;
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < num_leds; i++)
|
||||||
|
{
|
||||||
|
hsv_t hsv = {(uint8_t)(global_hue + (i * 7)), 255, 255};
|
||||||
|
rgb_t color = led_hsv_to_rgb(hsv);
|
||||||
|
|
||||||
|
if (i < num_leds_a)
|
||||||
|
{
|
||||||
|
led_set_pixel_a(num_leds_a - i - 1, color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
led_set_pixel_b(i - num_leds_a, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_glitter(uint8_t chance_of_glitter)
|
||||||
|
{
|
||||||
|
if (random8() < chance_of_glitter)
|
||||||
|
{
|
||||||
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||||
|
uint16_t pos = random16(num_leds);
|
||||||
|
rgb_t white = {255, 255, 255};
|
||||||
|
|
||||||
|
if (pos < led_get_num_leds_a())
|
||||||
|
{
|
||||||
|
led_add_pixel_a(pos, white);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
led_add_pixel_b(pos - led_get_num_leds_a(), white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_rainbow_glitter(void)
|
||||||
|
{
|
||||||
|
anim_rainbow();
|
||||||
|
add_glitter(255);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_confetti(void)
|
||||||
|
{
|
||||||
|
// Random colored speckles that blink in and fade smoothly
|
||||||
|
led_fade_to_black(10);
|
||||||
|
|
||||||
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||||
|
uint16_t pos = random16(num_leds);
|
||||||
|
|
||||||
|
hsv_t hsv = {(uint8_t)(global_hue + random8()), 255, 255};
|
||||||
|
rgb_t color = led_hsv_to_rgb(hsv);
|
||||||
|
|
||||||
|
if (pos < led_get_num_leds_a())
|
||||||
|
{
|
||||||
|
led_set_pixel_a(led_get_num_leds_a() - pos - 1, color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
led_set_pixel_b(pos - led_get_num_leds_a(), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_sinelon(void)
|
||||||
|
{
|
||||||
|
// A colored dot sweeping back and forth, with fading trails
|
||||||
|
led_fade_to_black(20);
|
||||||
|
|
||||||
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||||
|
int16_t pos = beatsin16(13, 0, num_leds);
|
||||||
|
|
||||||
|
hsv_t hsv = {global_hue, 255, 192};
|
||||||
|
rgb_t color = led_hsv_to_rgb(hsv);
|
||||||
|
|
||||||
|
if (pos < led_get_num_leds_a())
|
||||||
|
{
|
||||||
|
led_add_pixel_a(led_get_num_leds_a() - pos - 1, color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
led_add_pixel_b(pos - led_get_num_leds_a(), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_navigation(void)
|
||||||
|
{
|
||||||
|
// Aviation navigation lights with strobe overlay:
|
||||||
|
// - Red: Port (left) wingtip - steady
|
||||||
|
// - Green: Starboard (right) wingtip - steady
|
||||||
|
// - White strobe: Overlays outer nav lights with bright flashes
|
||||||
|
|
||||||
|
static uint8_t strobe_counter = 0;
|
||||||
|
led_clear_all();
|
||||||
|
|
||||||
|
uint16_t num_leds_a = led_get_num_leds_a();
|
||||||
|
uint16_t num_leds_b = led_get_num_leds_b();
|
||||||
|
|
||||||
|
rgb_t red = {255, 0, 0};
|
||||||
|
rgb_t green = {0, 255, 0};
|
||||||
|
rgb_t white = {255, 255, 255};
|
||||||
|
|
||||||
|
// Anti-collision strobe pattern: Double flash at ~1 Hz
|
||||||
|
// Flash duration: 3 frames (~50ms) for high-intensity effect
|
||||||
|
bool first_flash = (strobe_counter < 3);
|
||||||
|
bool second_flash = (strobe_counter >= 7 && strobe_counter < 10);
|
||||||
|
bool strobe_active = (first_flash || second_flash);
|
||||||
|
|
||||||
|
// Port (left) - Red navigation light OR white strobe (outer 3 LEDs of strip A)
|
||||||
|
if (num_leds_a >= 3)
|
||||||
|
{
|
||||||
|
rgb_t color_a = strobe_active ? white : red;
|
||||||
|
led_set_pixel_a(num_leds_a - 1, color_a);
|
||||||
|
led_set_pixel_a(num_leds_a - 2, red);
|
||||||
|
led_set_pixel_a(num_leds_a - 3, red);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starboard (right) - Green navigation light OR white strobe (outer 3 LEDs of strip B)
|
||||||
|
if (num_leds_b >= 3)
|
||||||
|
{
|
||||||
|
rgb_t color_b = strobe_active ? white : green;
|
||||||
|
led_set_pixel_b(num_leds_b - 1, color_b);
|
||||||
|
led_set_pixel_b(num_leds_b - 2, green);
|
||||||
|
led_set_pixel_b(num_leds_b - 3, green);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strobe cycle: 90 frames = 1.5 second at 60 FPS
|
||||||
|
strobe_counter = (strobe_counter + 1) % 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_chase(void)
|
||||||
|
{
|
||||||
|
// Red dot sweeping with trailing dots
|
||||||
|
led_clear_all();
|
||||||
|
|
||||||
|
uint16_t num_leds_a = led_get_num_leds_a();
|
||||||
|
uint16_t num_leds_b = led_get_num_leds_b();
|
||||||
|
uint16_t total_leds = num_leds_a + num_leds_b;
|
||||||
|
|
||||||
|
// Get oscillating position across both strips
|
||||||
|
int16_t center_pos = beatsin16(40, 0, total_leds - 1);
|
||||||
|
|
||||||
|
rgb_t red = {255, 0, 0};
|
||||||
|
|
||||||
|
// Draw center dot with dimmed trailing dots (3 dots total: center ±1)
|
||||||
|
for (int8_t offset = -1; offset <= 1; offset++)
|
||||||
|
{
|
||||||
|
int16_t led_pos = center_pos + offset;
|
||||||
|
|
||||||
|
// Skip if position is out of bounds
|
||||||
|
if (led_pos < 0 || led_pos >= total_leds)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Calculate brightness based on distance from center
|
||||||
|
uint8_t brightness = (offset == 0) ? 255 : 32; // Center: full, trailing: 12%
|
||||||
|
|
||||||
|
// Create dimmed color
|
||||||
|
rgb_t dimmed_red = {
|
||||||
|
(red.r * brightness) / 255,
|
||||||
|
(red.g * brightness) / 255,
|
||||||
|
(red.b * brightness) / 255};
|
||||||
|
|
||||||
|
// Map virtual position to physical LED
|
||||||
|
if (led_pos < num_leds_a)
|
||||||
|
{
|
||||||
|
// Strip A (mirrored: position 0 maps to last LED)
|
||||||
|
uint16_t strip_a_index = num_leds_a - led_pos - 1;
|
||||||
|
led_set_pixel_a(strip_a_index, dimmed_red);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Strip B (direct mapping)
|
||||||
|
uint16_t strip_b_index = led_pos - num_leds_a;
|
||||||
|
led_set_pixel_b(strip_b_index, dimmed_red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_chase_rgb(void)
|
||||||
|
{
|
||||||
|
// RGB cycling dot sweeping with trailing dots
|
||||||
|
led_clear_all();
|
||||||
|
|
||||||
|
uint16_t num_leds_a = led_get_num_leds_a();
|
||||||
|
uint16_t num_leds_b = led_get_num_leds_b();
|
||||||
|
uint16_t total_leds = num_leds_a + num_leds_b;
|
||||||
|
|
||||||
|
// Get oscillating position across both strips
|
||||||
|
int16_t center_pos = beatsin16(40, 0, total_leds - 1);
|
||||||
|
|
||||||
|
hsv_t hsv = {global_hue, 255, 192};
|
||||||
|
rgb_t color = led_hsv_to_rgb(hsv);
|
||||||
|
|
||||||
|
// Draw center dot with dimmed trailing dots (3 dots total: center ±1)
|
||||||
|
for (int8_t offset = -1; offset <= 1; offset++)
|
||||||
|
{
|
||||||
|
int16_t led_pos = center_pos + offset;
|
||||||
|
|
||||||
|
// Skip if position is out of bounds
|
||||||
|
if (led_pos < 0 || led_pos >= total_leds)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Calculate brightness based on distance from center
|
||||||
|
uint8_t brightness = (offset == 0) ? 255 : 32; // Center: full, trailing: 12%
|
||||||
|
|
||||||
|
// Create dimmed color
|
||||||
|
rgb_t dimmed_color = {
|
||||||
|
(color.r * brightness) / 255,
|
||||||
|
(color.g * brightness) / 255,
|
||||||
|
(color.b * brightness) / 255};
|
||||||
|
|
||||||
|
// Map virtual position to physical LED
|
||||||
|
if (led_pos < num_leds_a)
|
||||||
|
{
|
||||||
|
// Strip A (mirrored: position 0 maps to last LED)
|
||||||
|
uint16_t strip_a_index = num_leds_a - led_pos - 1;
|
||||||
|
led_set_pixel_a(strip_a_index, dimmed_color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Strip B (direct mapping)
|
||||||
|
uint16_t strip_b_index = led_pos - num_leds_a;
|
||||||
|
led_set_pixel_b(strip_b_index, dimmed_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anim_random(void)
|
||||||
|
{
|
||||||
|
// Random LEDs get random colors
|
||||||
|
uint16_t num_leds = led_get_num_leds_a() + led_get_num_leds_b();
|
||||||
|
uint16_t random_pos = random16(num_leds);
|
||||||
|
|
||||||
|
rgb_t random_color = {
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0};
|
||||||
|
|
||||||
|
// Set random LED to random basis color
|
||||||
|
switch (random16(3))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
random_color.r = 255;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
random_color.g = 255;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
random_color.b = 255;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (random_pos < led_get_num_leds_a())
|
||||||
|
{
|
||||||
|
led_set_pixel_a(random_pos, random_color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
led_set_pixel_b(random_pos - led_get_num_leds_a(), random_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t animation_init(void)
|
||||||
|
{
|
||||||
|
current_mode = ANIM_BLACK;
|
||||||
|
global_hue = 0U;
|
||||||
|
frame_counter = 0U;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Animation initialized");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void animation_set_mode(animation_mode_t mode)
|
||||||
|
{
|
||||||
|
if ((mode >= ANIM_MODE_COUNT) || (mode < 0U))
|
||||||
|
{
|
||||||
|
mode = ANIM_BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_mode = mode;
|
||||||
|
frame_counter = 0U;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Animation mode set to: %s", animation_get_mode_name(mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
void animation_update(void)
|
||||||
|
{
|
||||||
|
// Update global hue every frame (slowly cycles colors)
|
||||||
|
frame_counter++;
|
||||||
|
if (frame_counter % 3 == 0)
|
||||||
|
{
|
||||||
|
global_hue++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute current animation
|
||||||
|
switch (current_mode)
|
||||||
|
{
|
||||||
|
case ANIM_BLACK:
|
||||||
|
anim_black();
|
||||||
|
break;
|
||||||
|
case ANIM_RED:
|
||||||
|
anim_red();
|
||||||
|
break;
|
||||||
|
case ANIM_BLUE:
|
||||||
|
anim_blue();
|
||||||
|
break;
|
||||||
|
case ANIM_GREEN:
|
||||||
|
anim_green();
|
||||||
|
break;
|
||||||
|
case ANIM_WHITE:
|
||||||
|
anim_white();
|
||||||
|
break;
|
||||||
|
case ANIM_RAINBOW:
|
||||||
|
anim_rainbow();
|
||||||
|
break;
|
||||||
|
case ANIM_RAINBOW_GLITTER:
|
||||||
|
anim_rainbow_glitter();
|
||||||
|
break;
|
||||||
|
case ANIM_CONFETTI:
|
||||||
|
anim_confetti();
|
||||||
|
break;
|
||||||
|
case ANIM_SINELON:
|
||||||
|
anim_sinelon();
|
||||||
|
break;
|
||||||
|
case ANIM_NAVIGATION:
|
||||||
|
anim_navigation();
|
||||||
|
break;
|
||||||
|
case ANIM_CHASE:
|
||||||
|
anim_chase();
|
||||||
|
break;
|
||||||
|
case ANIM_CHASE_RGB:
|
||||||
|
anim_chase_rgb();
|
||||||
|
break;
|
||||||
|
case ANIM_RANDOM:
|
||||||
|
anim_random();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
anim_black();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
led_show();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *animation_get_mode_name(animation_mode_t mode)
|
||||||
|
{
|
||||||
|
static const char *mode_names[] = {
|
||||||
|
"Black",
|
||||||
|
"Red",
|
||||||
|
"Blue",
|
||||||
|
"Green",
|
||||||
|
"White",
|
||||||
|
"Rainbow",
|
||||||
|
"Rainbow with Glitter",
|
||||||
|
"Confetti",
|
||||||
|
"Sinelon",
|
||||||
|
"Navigation",
|
||||||
|
"Chase",
|
||||||
|
"Chase RGB",
|
||||||
|
"Random"};
|
||||||
|
|
||||||
|
if (mode >= ANIM_MODE_COUNT)
|
||||||
|
{
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode_names[mode];
|
||||||
|
}
|
||||||
57
main/animation.h
Normal file
57
main/animation.h
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* @file animation.h
|
||||||
|
* @brief LED animation patterns
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ANIMATION_H
|
||||||
|
#define ANIMATION_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Animation modes
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
ANIM_BLACK = 0, // All off
|
||||||
|
ANIM_RED = 1, // All red
|
||||||
|
ANIM_BLUE = 2, // All blue
|
||||||
|
ANIM_GREEN = 3, // All green
|
||||||
|
ANIM_WHITE = 4, // All white
|
||||||
|
ANIM_RAINBOW = 5, // FastLED rainbow
|
||||||
|
ANIM_RAINBOW_GLITTER = 6, // Rainbow with glitter
|
||||||
|
ANIM_CONFETTI = 7, // Random colored speckles
|
||||||
|
ANIM_SINELON = 8, // Colored dot sweeping (RGB cycling)
|
||||||
|
ANIM_NAVIGATION = 9, // Navigation lights (red left, green right)
|
||||||
|
ANIM_CHASE = 10, // Red dot sweeping
|
||||||
|
ANIM_CHASE_RGB = 11, // RGB cycling dot sweeping
|
||||||
|
ANIM_RANDOM = 12, // Random mode
|
||||||
|
ANIM_MODE_COUNT
|
||||||
|
} animation_mode_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize animation system
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t animation_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set current animation mode
|
||||||
|
* @param mode Animation mode
|
||||||
|
*/
|
||||||
|
void animation_set_mode(animation_mode_t mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update animation (call periodically, e.g., 30-60 FPS)
|
||||||
|
*/
|
||||||
|
void animation_update(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get animation mode name
|
||||||
|
* @param mode Animation mode
|
||||||
|
* @return Mode name string
|
||||||
|
*/
|
||||||
|
const char *animation_get_mode_name(animation_mode_t mode);
|
||||||
|
|
||||||
|
#endif // ANIMATION_H
|
||||||
199
main/config.c
Normal file
199
main/config.c
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/**
|
||||||
|
* @file config.c
|
||||||
|
* @brief Config module implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "nvs.h"
|
||||||
|
#include "soc/gpio_num.h"
|
||||||
|
#include "mbedtls/sha256.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const char *TAG = "CONFIG";
|
||||||
|
|
||||||
|
#define NVS_NAMESPACE "led_ctrl"
|
||||||
|
|
||||||
|
#define HARDCODED_CONFIG
|
||||||
|
#ifdef HARDCODED_CONFIG
|
||||||
|
#define HARDCODED_CONFIG_LED_STRIP_A_PIN 3U
|
||||||
|
#define HARDCODED_CONFIG_LED_STRIP_B_PIN 2U
|
||||||
|
#define HARDCODED_CONFIG_LED_STRIP_A_COUNT 10U
|
||||||
|
#define HARDCODED_CONFIG_LED_STRIP_B_COUNT 10U
|
||||||
|
#define HARDCODED_CONFIG_PWM_PIN 1U
|
||||||
|
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
|
#define HARDCODED_CONFIG_LOCALBTN_PIN 9
|
||||||
|
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||||
|
#define HARDCODED_CONFIG_LOCALBTN_PIN 0
|
||||||
|
#else
|
||||||
|
#error "Unsupported target: BOOT button GPIO not defined"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Global state
|
||||||
|
static config_t current_config = {
|
||||||
|
.led_pin_strip_a = -1,
|
||||||
|
.led_pin_strip_b = -1,
|
||||||
|
.led_count_strip_a = -1,
|
||||||
|
.led_count_strip_b = -1,
|
||||||
|
.pwm_pin = -1,
|
||||||
|
.localBtn_pin = -1};
|
||||||
|
|
||||||
|
static void calculate_config_hash(const config_t *cfg, uint8_t *out_hash);
|
||||||
|
|
||||||
|
// NVS Functions
|
||||||
|
static esp_err_t load_config_from_nvs(void)
|
||||||
|
{
|
||||||
|
nvs_handle_t nvs_handle;
|
||||||
|
size_t size = sizeof(config_t);
|
||||||
|
config_t tmp;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 2U; i++)
|
||||||
|
{
|
||||||
|
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "NVS not found, using defaults");
|
||||||
|
config_reset_config();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nvs_get_blob(nvs_handle, "config", &tmp, &size);
|
||||||
|
nvs_close(nvs_handle);
|
||||||
|
|
||||||
|
uint8_t calc_hash[CONFIG_HASH_LEN];
|
||||||
|
calculate_config_hash(&tmp, calc_hash);
|
||||||
|
|
||||||
|
if (memcmp(calc_hash, tmp.hash, CONFIG_HASH_LEN) != 0)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Invalid config in NVS, using defaults");
|
||||||
|
config_reset_config();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found a valid config
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Loaded config from NVS");
|
||||||
|
ESP_LOGI(TAG, " Strip A: GPIO%d", current_config.led_pin_strip_a);
|
||||||
|
ESP_LOGI(TAG, " Strip B: GPIO%d", current_config.led_pin_strip_b);
|
||||||
|
ESP_LOGI(TAG, " Strip A LED count: %d", current_config.led_count_strip_a);
|
||||||
|
ESP_LOGI(TAG, " Strip B LED count: %d", current_config.led_count_strip_b);
|
||||||
|
ESP_LOGI(TAG, " PWM Pin: GPIO%d", current_config.pwm_pin);
|
||||||
|
ESP_LOGI(TAG, " Local btn Pin: GPIO%d", current_config.localBtn_pin);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t save_config_to_nvs(void)
|
||||||
|
{
|
||||||
|
calculate_config_hash(¤t_config, current_config.hash);
|
||||||
|
|
||||||
|
nvs_handle_t nvs_handle;
|
||||||
|
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nvs_set_blob(nvs_handle, "config", ¤t_config, sizeof(config_t));
|
||||||
|
if (err == ESP_OK)
|
||||||
|
{
|
||||||
|
err = nvs_commit(nvs_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
nvs_close(nvs_handle);
|
||||||
|
|
||||||
|
if (err == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Config saved to NVS");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to save config: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t config_reset_config(void)
|
||||||
|
{
|
||||||
|
current_config.led_pin_strip_a = -1;
|
||||||
|
current_config.led_pin_strip_b = -1;
|
||||||
|
current_config.led_count_strip_a = -1;
|
||||||
|
current_config.led_count_strip_b = -1;
|
||||||
|
current_config.pwm_pin = -1;
|
||||||
|
current_config.localBtn_pin = -1;
|
||||||
|
|
||||||
|
return save_config_to_nvs();
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_get_config(config_t *const cnf)
|
||||||
|
{
|
||||||
|
cnf->led_pin_strip_a = current_config.led_pin_strip_a;
|
||||||
|
cnf->led_pin_strip_b = current_config.led_pin_strip_b;
|
||||||
|
cnf->led_count_strip_a = current_config.led_count_strip_a;
|
||||||
|
cnf->led_count_strip_b = current_config.led_count_strip_b;
|
||||||
|
cnf->pwm_pin = current_config.pwm_pin;
|
||||||
|
cnf->localBtn_pin = current_config.localBtn_pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t config_init(void)
|
||||||
|
{
|
||||||
|
esp_err_t ret;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Initializing Config...");
|
||||||
|
|
||||||
|
// Initialize NVS
|
||||||
|
ret = nvs_flash_init();
|
||||||
|
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
||||||
|
{
|
||||||
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||||
|
ret = nvs_flash_init();
|
||||||
|
ESP_ERROR_CHECK(config_reset_config());
|
||||||
|
}
|
||||||
|
ESP_ERROR_CHECK(ret);
|
||||||
|
|
||||||
|
#ifdef HARDCODED_CONFIG
|
||||||
|
current_config.led_pin_strip_a = HARDCODED_CONFIG_LED_STRIP_A_PIN;
|
||||||
|
current_config.led_pin_strip_b = HARDCODED_CONFIG_LED_STRIP_B_PIN;
|
||||||
|
current_config.led_count_strip_a = HARDCODED_CONFIG_LED_STRIP_A_COUNT;
|
||||||
|
current_config.led_count_strip_b = HARDCODED_CONFIG_LED_STRIP_B_COUNT;
|
||||||
|
current_config.pwm_pin = HARDCODED_CONFIG_PWM_PIN;
|
||||||
|
current_config.localBtn_pin = HARDCODED_CONFIG_LOCALBTN_PIN;
|
||||||
|
|
||||||
|
save_config_to_nvs();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Load configuration
|
||||||
|
load_config_from_nvs();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Config initialized successfully");
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void calculate_config_hash(const config_t *cfg, uint8_t *out_hash)
|
||||||
|
{
|
||||||
|
mbedtls_sha256_context ctx;
|
||||||
|
|
||||||
|
mbedtls_sha256_init(&ctx);
|
||||||
|
mbedtls_sha256_starts(&ctx, 0); // 0 = SHA-256, 1 = SHA-224
|
||||||
|
|
||||||
|
mbedtls_sha256_update(
|
||||||
|
&ctx,
|
||||||
|
(const unsigned char *)cfg,
|
||||||
|
offsetof(config_t, hash));
|
||||||
|
|
||||||
|
mbedtls_sha256_finish(&ctx, out_hash);
|
||||||
|
mbedtls_sha256_free(&ctx);
|
||||||
|
}
|
||||||
48
main/config.h
Normal file
48
main/config.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* @file config.h
|
||||||
|
* @brief Config module for LED controller - handles read and store of persistent data
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define CONFIG_HASH_LEN 32 // SHA256
|
||||||
|
/**
|
||||||
|
* @brief Configuration structure stored in NVS
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int8_t led_pin_strip_a; // GPIO pin for LED strip A (-1 = not configured)
|
||||||
|
int8_t led_pin_strip_b; // GPIO pin for LED strip B (-1 = not configured)
|
||||||
|
int8_t led_count_strip_a; // LED count for LED strip A (-1 = not configured)
|
||||||
|
int8_t led_count_strip_b; // LED count for LED strip B (-1 = not configured)
|
||||||
|
int8_t pwm_pin; // GPIO pin for PWM input (-1 = not configured)
|
||||||
|
int8_t localBtn_pin; // GPIO pin for local btn input (-1 = not configured)
|
||||||
|
uint8_t hash[CONFIG_HASH_LEN]; // SHA256 Hash of config
|
||||||
|
} config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the config system
|
||||||
|
* Loads configuration from NVS
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t config_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current configuration
|
||||||
|
* @param Pointer to current configuration (read-only)
|
||||||
|
*/
|
||||||
|
void config_get_config(config_t *const cnf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset configuration to defaults
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t config_reset_config(void);
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
||||||
90
main/control.c
Normal file
90
main/control.c
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* @file control.c
|
||||||
|
* @brief Control module implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "led.h"
|
||||||
|
#include "rcsignal.h"
|
||||||
|
#include "localbtn.h"
|
||||||
|
#include "animation.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
static const char *TAG = "CONTROL";
|
||||||
|
static uint8_t current_animation_mode = 0;
|
||||||
|
|
||||||
|
// Animation mode change callback
|
||||||
|
static void on_mode_change()
|
||||||
|
{
|
||||||
|
current_animation_mode = (current_animation_mode + 1) % ANIM_MODE_COUNT;
|
||||||
|
animation_set_mode((animation_mode_t)current_animation_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t control_get_animation_mode(void)
|
||||||
|
{
|
||||||
|
return current_animation_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main initialization
|
||||||
|
esp_err_t control_init(void)
|
||||||
|
{
|
||||||
|
esp_err_t ret;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Initializing LED Controller...");
|
||||||
|
|
||||||
|
// Initialize config
|
||||||
|
ret = config_init();
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Config init failed: %s", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
config_t current_config;
|
||||||
|
config_get_config(¤t_config);
|
||||||
|
|
||||||
|
// Initialize LED strips
|
||||||
|
ret = led_init(current_config.led_pin_strip_a, current_config.led_pin_strip_b,
|
||||||
|
current_config.led_count_strip_a, current_config.led_count_strip_b);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "LED init failed: %s", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize animation system
|
||||||
|
ret = animation_init();
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Animation init failed: %s", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize RC signal
|
||||||
|
ret = rcsignal_init(current_config.pwm_pin);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "RC signal init failed: %s", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize local BTN
|
||||||
|
ret = localbtn_init(current_config.localBtn_pin);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Local BTN init failed: %s", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register mode change callback
|
||||||
|
rcsignal_register_callback(on_mode_change);
|
||||||
|
localbtn_register_callback(on_mode_change);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Control system initialized successfully");
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
27
main/control.h
Normal file
27
main/control.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @file control.h
|
||||||
|
* @brief Control module for LED controller - handles initialization of LEDs, PWM
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CONTROL_H
|
||||||
|
#define CONTROL_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the control system
|
||||||
|
* Loads configuration from NVS and initializes subsystems
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t control_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current animation mode
|
||||||
|
* @return Current mode (0-13)
|
||||||
|
*/
|
||||||
|
uint8_t control_get_animation_mode(void);
|
||||||
|
|
||||||
|
#endif // CONTROL_H
|
||||||
495
main/led.c
Normal file
495
main/led.c
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
/**
|
||||||
|
* @file led.c
|
||||||
|
* @brief WS2812B LED strip control implementation using RMT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "led.h"
|
||||||
|
|
||||||
|
#include "driver/rmt_tx.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static const char *TAG = "LED";
|
||||||
|
|
||||||
|
// WS2812B timing (in nanoseconds)
|
||||||
|
#define WS2812_T0H_NS 350
|
||||||
|
#define WS2812_T0L_NS 900
|
||||||
|
#define WS2812_T1H_NS 900
|
||||||
|
#define WS2812_T1L_NS 350
|
||||||
|
#define WS2812_RESET_US 280
|
||||||
|
|
||||||
|
// LED strip data structures
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rmt_channel_handle_t rmt_channel;
|
||||||
|
rmt_encoder_handle_t encoder;
|
||||||
|
rgb_t *buffer;
|
||||||
|
uint16_t num_leds;
|
||||||
|
int8_t gpio_pin;
|
||||||
|
bool initialized;
|
||||||
|
} led_strip_t;
|
||||||
|
|
||||||
|
static led_strip_t strip_a = {0};
|
||||||
|
static led_strip_t strip_b = {0};
|
||||||
|
static SemaphoreHandle_t led_mutex = NULL;
|
||||||
|
|
||||||
|
// RMT encoder for WS2812B
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rmt_encoder_t base;
|
||||||
|
rmt_encoder_t *bytes_encoder;
|
||||||
|
rmt_encoder_t *copy_encoder;
|
||||||
|
int state;
|
||||||
|
rmt_symbol_word_t reset_code;
|
||||||
|
} rmt_led_strip_encoder_t;
|
||||||
|
|
||||||
|
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel,
|
||||||
|
const void *primary_data, size_t data_size,
|
||||||
|
rmt_encode_state_t *ret_state)
|
||||||
|
{
|
||||||
|
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||||
|
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
|
||||||
|
rmt_encode_state_t state = RMT_ENCODING_RESET;
|
||||||
|
size_t encoded_symbols = 0;
|
||||||
|
|
||||||
|
switch (led_encoder->state)
|
||||||
|
{
|
||||||
|
case 0: // send RGB data
|
||||||
|
encoded_symbols += led_encoder->bytes_encoder->encode(led_encoder->bytes_encoder, channel,
|
||||||
|
primary_data, data_size, &session_state);
|
||||||
|
if (session_state & RMT_ENCODING_COMPLETE)
|
||||||
|
{
|
||||||
|
led_encoder->state = 1; // switch to next state when current encoding session finished
|
||||||
|
}
|
||||||
|
if (session_state & RMT_ENCODING_MEM_FULL)
|
||||||
|
{
|
||||||
|
state |= RMT_ENCODING_MEM_FULL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
// fall-through
|
||||||
|
case 1: // send reset code
|
||||||
|
encoded_symbols += led_encoder->copy_encoder->encode(led_encoder->copy_encoder, channel,
|
||||||
|
&led_encoder->reset_code,
|
||||||
|
sizeof(led_encoder->reset_code), &session_state);
|
||||||
|
if (session_state & RMT_ENCODING_COMPLETE)
|
||||||
|
{
|
||||||
|
led_encoder->state = RMT_ENCODING_RESET;
|
||||||
|
state |= RMT_ENCODING_COMPLETE;
|
||||||
|
}
|
||||||
|
if (session_state & RMT_ENCODING_MEM_FULL)
|
||||||
|
{
|
||||||
|
state |= RMT_ENCODING_MEM_FULL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
*ret_state = state;
|
||||||
|
return encoded_symbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
|
||||||
|
{
|
||||||
|
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||||
|
rmt_del_encoder(led_encoder->bytes_encoder);
|
||||||
|
rmt_del_encoder(led_encoder->copy_encoder);
|
||||||
|
free(led_encoder);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
|
||||||
|
{
|
||||||
|
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||||
|
rmt_encoder_reset(led_encoder->bytes_encoder);
|
||||||
|
rmt_encoder_reset(led_encoder->copy_encoder);
|
||||||
|
led_encoder->state = RMT_ENCODING_RESET;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rmt_new_led_strip_encoder(rmt_encoder_handle_t *ret_encoder)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
rmt_led_strip_encoder_t *led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t));
|
||||||
|
if (!led_encoder)
|
||||||
|
{
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
led_encoder->base.encode = rmt_encode_led_strip;
|
||||||
|
led_encoder->base.del = rmt_del_led_strip_encoder;
|
||||||
|
led_encoder->base.reset = rmt_led_strip_encoder_reset;
|
||||||
|
|
||||||
|
// WS2812 timing
|
||||||
|
rmt_bytes_encoder_config_t bytes_encoder_config = {
|
||||||
|
.bit0 = {
|
||||||
|
.level0 = 1,
|
||||||
|
.duration0 = WS2812_T0H_NS * 80 / 1000, // 80MHz clock
|
||||||
|
.level1 = 0,
|
||||||
|
.duration1 = WS2812_T0L_NS * 80 / 1000,
|
||||||
|
},
|
||||||
|
.bit1 = {
|
||||||
|
.level0 = 1,
|
||||||
|
.duration0 = WS2812_T1H_NS * 80 / 1000,
|
||||||
|
.level1 = 0,
|
||||||
|
.duration1 = WS2812_T1L_NS * 80 / 1000,
|
||||||
|
},
|
||||||
|
.flags.msb_first = 1,
|
||||||
|
};
|
||||||
|
ret = rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
rmt_copy_encoder_config_t copy_encoder_config = {};
|
||||||
|
ret = rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t reset_ticks = WS2812_RESET_US * 80; // 80MHz
|
||||||
|
led_encoder->reset_code = (rmt_symbol_word_t){
|
||||||
|
.level0 = 0,
|
||||||
|
.duration0 = reset_ticks & 0x7FFF,
|
||||||
|
.level1 = 0,
|
||||||
|
.duration1 = reset_ticks & 0x7FFF,
|
||||||
|
};
|
||||||
|
|
||||||
|
*ret_encoder = &led_encoder->base;
|
||||||
|
return ESP_OK;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (led_encoder->bytes_encoder)
|
||||||
|
{
|
||||||
|
rmt_del_encoder(led_encoder->bytes_encoder);
|
||||||
|
}
|
||||||
|
if (led_encoder->copy_encoder)
|
||||||
|
{
|
||||||
|
rmt_del_encoder(led_encoder->copy_encoder);
|
||||||
|
}
|
||||||
|
free(led_encoder);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t init_strip(led_strip_t *strip, int8_t pin, uint16_t num_leds)
|
||||||
|
{
|
||||||
|
if (pin < 0 || num_leds == 0)
|
||||||
|
{
|
||||||
|
return ESP_OK; // Skip if not configured
|
||||||
|
}
|
||||||
|
|
||||||
|
strip->buffer = calloc(num_leds, sizeof(rgb_t));
|
||||||
|
if (!strip->buffer)
|
||||||
|
{
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
strip->num_leds = num_leds;
|
||||||
|
strip->gpio_pin = pin;
|
||||||
|
|
||||||
|
rmt_tx_channel_config_t tx_chan_config = {
|
||||||
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
|
.gpio_num = pin,
|
||||||
|
.mem_block_symbols = 48,
|
||||||
|
.resolution_hz = 80000000, // 80MHz
|
||||||
|
.trans_queue_depth = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &strip->rmt_channel));
|
||||||
|
ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&strip->encoder));
|
||||||
|
ESP_ERROR_CHECK(rmt_enable(strip->rmt_channel));
|
||||||
|
|
||||||
|
strip->initialized = true;
|
||||||
|
ESP_LOGI(TAG, "Initialized strip on GPIO%d with %d LEDs", pin, num_leds);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t led_init(int8_t pin_a, int8_t pin_b, uint16_t num_leds_a, uint16_t num_leds_b)
|
||||||
|
{
|
||||||
|
if (led_mutex == NULL)
|
||||||
|
{
|
||||||
|
led_mutex = xSemaphoreCreateMutex();
|
||||||
|
if (!led_mutex)
|
||||||
|
{
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
if (pin_a >= 0)
|
||||||
|
{
|
||||||
|
ret = init_strip(&strip_a, pin_a, num_leds_a);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to init strip A: %s", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin_b >= 0)
|
||||||
|
{
|
||||||
|
ret = init_strip(&strip_b, pin_b, num_leds_b);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to init strip B: %s", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_deinit(void)
|
||||||
|
{
|
||||||
|
if (strip_a.initialized)
|
||||||
|
{
|
||||||
|
rmt_disable(strip_a.rmt_channel);
|
||||||
|
rmt_del_channel(strip_a.rmt_channel);
|
||||||
|
free(strip_a.buffer);
|
||||||
|
strip_a.initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strip_b.initialized)
|
||||||
|
{
|
||||||
|
rmt_disable(strip_b.rmt_channel);
|
||||||
|
rmt_del_channel(strip_b.rmt_channel);
|
||||||
|
free(strip_b.buffer);
|
||||||
|
strip_b.initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_set_pixel_a(uint16_t index, rgb_t color)
|
||||||
|
{
|
||||||
|
if (!strip_a.initialized || index >= strip_a.num_leds)
|
||||||
|
return;
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
strip_a.buffer[index] = color;
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_set_pixel_b(uint16_t index, rgb_t color)
|
||||||
|
{
|
||||||
|
if (!strip_b.initialized || index >= strip_b.num_leds)
|
||||||
|
return;
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
strip_b.buffer[index] = color;
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_fill_a(rgb_t color)
|
||||||
|
{
|
||||||
|
if (!strip_a.initialized)
|
||||||
|
return;
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
for (uint16_t i = 0; i < strip_a.num_leds; i++)
|
||||||
|
{
|
||||||
|
strip_a.buffer[i] = color;
|
||||||
|
}
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_fill_b(rgb_t color)
|
||||||
|
{
|
||||||
|
if (!strip_b.initialized)
|
||||||
|
return;
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
for (uint16_t i = 0; i < strip_b.num_leds; i++)
|
||||||
|
{
|
||||||
|
strip_b.buffer[i] = color;
|
||||||
|
}
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_clear_all(void)
|
||||||
|
{
|
||||||
|
rgb_t black = {0, 0, 0};
|
||||||
|
led_fill_a(black);
|
||||||
|
led_fill_b(black);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_strip(led_strip_t *strip)
|
||||||
|
{
|
||||||
|
if (!strip->initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Convert RGB to GRB for WS2812B
|
||||||
|
uint8_t *grb_data = malloc(strip->num_leds * 3);
|
||||||
|
if (!grb_data)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate GRB buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < strip->num_leds; i++)
|
||||||
|
{
|
||||||
|
grb_data[i * 3 + 0] = strip->buffer[i].g;
|
||||||
|
grb_data[i * 3 + 1] = strip->buffer[i].r;
|
||||||
|
grb_data[i * 3 + 2] = strip->buffer[i].b;
|
||||||
|
}
|
||||||
|
|
||||||
|
rmt_transmit_config_t tx_config = {
|
||||||
|
.loop_count = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t ret = rmt_transmit(strip->rmt_channel, strip->encoder, grb_data, strip->num_leds * 3, &tx_config);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "RMT transmit failed: %s", esp_err_to_name(ret));
|
||||||
|
free(grb_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for transmission to complete before freeing buffer
|
||||||
|
ret = rmt_tx_wait_all_done(strip->rmt_channel, pdMS_TO_TICKS(100));
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "RMT wait timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
free(grb_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_show(void)
|
||||||
|
{
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
show_strip(&strip_a);
|
||||||
|
show_strip(&strip_b);
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_fade_to_black(uint8_t amount)
|
||||||
|
{
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
|
||||||
|
if (strip_a.initialized)
|
||||||
|
{
|
||||||
|
for (uint16_t i = 0; i < strip_a.num_leds; i++)
|
||||||
|
{
|
||||||
|
strip_a.buffer[i].r = (strip_a.buffer[i].r * (255 - amount)) / 255;
|
||||||
|
strip_a.buffer[i].g = (strip_a.buffer[i].g * (255 - amount)) / 255;
|
||||||
|
strip_a.buffer[i].b = (strip_a.buffer[i].b * (255 - amount)) / 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strip_b.initialized)
|
||||||
|
{
|
||||||
|
for (uint16_t i = 0; i < strip_b.num_leds; i++)
|
||||||
|
{
|
||||||
|
strip_b.buffer[i].r = (strip_b.buffer[i].r * (255 - amount)) / 255;
|
||||||
|
strip_b.buffer[i].g = (strip_b.buffer[i].g * (255 - amount)) / 255;
|
||||||
|
strip_b.buffer[i].b = (strip_b.buffer[i].b * (255 - amount)) / 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
rgb_t led_hsv_to_rgb(hsv_t hsv)
|
||||||
|
{
|
||||||
|
rgb_t rgb = {0};
|
||||||
|
uint8_t region, remainder, p, q, t;
|
||||||
|
|
||||||
|
if (hsv.s == 0)
|
||||||
|
{
|
||||||
|
rgb.r = hsv.v;
|
||||||
|
rgb.g = hsv.v;
|
||||||
|
rgb.b = hsv.v;
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
region = hsv.h / 43;
|
||||||
|
remainder = (hsv.h - (region * 43)) * 6;
|
||||||
|
|
||||||
|
p = (hsv.v * (255 - hsv.s)) >> 8;
|
||||||
|
q = (hsv.v * (255 - ((hsv.s * remainder) >> 8))) >> 8;
|
||||||
|
t = (hsv.v * (255 - ((hsv.s * (255 - remainder)) >> 8))) >> 8;
|
||||||
|
|
||||||
|
switch (region)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
rgb.r = hsv.v;
|
||||||
|
rgb.g = t;
|
||||||
|
rgb.b = p;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
rgb.r = q;
|
||||||
|
rgb.g = hsv.v;
|
||||||
|
rgb.b = p;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
rgb.r = p;
|
||||||
|
rgb.g = hsv.v;
|
||||||
|
rgb.b = t;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
rgb.r = p;
|
||||||
|
rgb.g = q;
|
||||||
|
rgb.b = hsv.v;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
rgb.r = t;
|
||||||
|
rgb.g = p;
|
||||||
|
rgb.b = hsv.v;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rgb.r = hsv.v;
|
||||||
|
rgb.g = p;
|
||||||
|
rgb.b = q;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t led_get_num_leds_a(void) { return strip_a.num_leds; }
|
||||||
|
uint16_t led_get_num_leds_b(void) { return strip_b.num_leds; }
|
||||||
|
|
||||||
|
rgb_t led_get_pixel_a(uint16_t index)
|
||||||
|
{
|
||||||
|
rgb_t color = {0};
|
||||||
|
if (!strip_a.initialized || index >= strip_a.num_leds)
|
||||||
|
return color;
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
color = strip_a.buffer[index];
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
rgb_t led_get_pixel_b(uint16_t index)
|
||||||
|
{
|
||||||
|
rgb_t color = {0};
|
||||||
|
if (!strip_b.initialized || index >= strip_b.num_leds)
|
||||||
|
return color;
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
color = strip_b.buffer[index];
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_add_pixel_a(uint16_t index, rgb_t color)
|
||||||
|
{
|
||||||
|
if (!strip_a.initialized || index >= strip_a.num_leds)
|
||||||
|
return;
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
strip_a.buffer[index].r = (strip_a.buffer[index].r + color.r > 255) ? 255 : strip_a.buffer[index].r + color.r;
|
||||||
|
strip_a.buffer[index].g = (strip_a.buffer[index].g + color.g > 255) ? 255 : strip_a.buffer[index].g + color.g;
|
||||||
|
strip_a.buffer[index].b = (strip_a.buffer[index].b + color.b > 255) ? 255 : strip_a.buffer[index].b + color.b;
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_add_pixel_b(uint16_t index, rgb_t color)
|
||||||
|
{
|
||||||
|
if (!strip_b.initialized || index >= strip_b.num_leds)
|
||||||
|
return;
|
||||||
|
xSemaphoreTake(led_mutex, portMAX_DELAY);
|
||||||
|
strip_b.buffer[index].r = (strip_b.buffer[index].r + color.r > 255) ? 255 : strip_b.buffer[index].r + color.r;
|
||||||
|
strip_b.buffer[index].g = (strip_b.buffer[index].g + color.g > 255) ? 255 : strip_b.buffer[index].g + color.g;
|
||||||
|
strip_b.buffer[index].b = (strip_b.buffer[index].b + color.b > 255) ? 255 : strip_b.buffer[index].b + color.b;
|
||||||
|
xSemaphoreGive(led_mutex);
|
||||||
|
}
|
||||||
137
main/led.h
Normal file
137
main/led.h
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* @file led.h
|
||||||
|
* @brief LED strip control module for WS2812B
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LED_H
|
||||||
|
#define LED_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define LED_STRIP_MAX_LEDS 100 // Maximum LEDs per strip
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RGB color structure
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
} rgb_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HSV color structure
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t h; // Hue: 0-255
|
||||||
|
uint8_t s; // Saturation: 0-255
|
||||||
|
uint8_t v; // Value/Brightness: 0-255
|
||||||
|
} hsv_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize LED strips
|
||||||
|
* @param pin_a GPIO pin for strip A (-1 to disable)
|
||||||
|
* @param pin_b GPIO pin for strip B (-1 to disable)
|
||||||
|
* @param num_leds_a Number of LEDs in strip A
|
||||||
|
* @param num_leds_b Number of LEDs in strip B
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t led_init(int8_t pin_a, int8_t pin_b, uint16_t num_leds_a, uint16_t num_leds_b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deinitialize LED strips
|
||||||
|
*/
|
||||||
|
void led_deinit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set pixel color on strip A
|
||||||
|
* @param index LED index
|
||||||
|
* @param color RGB color
|
||||||
|
*/
|
||||||
|
void led_set_pixel_a(uint16_t index, rgb_t color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set pixel color on strip B
|
||||||
|
* @param index LED index
|
||||||
|
* @param color RGB color
|
||||||
|
*/
|
||||||
|
void led_set_pixel_b(uint16_t index, rgb_t color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set all pixels on strip A to same color
|
||||||
|
* @param color RGB color
|
||||||
|
*/
|
||||||
|
void led_fill_a(rgb_t color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set all pixels on strip B to same color
|
||||||
|
* @param color RGB color
|
||||||
|
*/
|
||||||
|
void led_fill_b(rgb_t color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear all pixels on both strips (set to black)
|
||||||
|
*/
|
||||||
|
void led_clear_all(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Refresh/update LED strips to show changes
|
||||||
|
*/
|
||||||
|
void led_show(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fade all pixels towards black
|
||||||
|
* @param amount Fade amount (0-255)
|
||||||
|
*/
|
||||||
|
void led_fade_to_black(uint8_t amount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert HSV to RGB
|
||||||
|
* @param hsv HSV color
|
||||||
|
* @return RGB color
|
||||||
|
*/
|
||||||
|
rgb_t led_hsv_to_rgb(hsv_t hsv);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get number of LEDs in strip A
|
||||||
|
* @return Number of LEDs
|
||||||
|
*/
|
||||||
|
uint16_t led_get_num_leds_a(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get number of LEDs in strip B
|
||||||
|
* @return Number of LEDs
|
||||||
|
*/
|
||||||
|
uint16_t led_get_num_leds_b(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current color of pixel on strip A
|
||||||
|
* @param index LED index
|
||||||
|
* @return RGB color
|
||||||
|
*/
|
||||||
|
rgb_t led_get_pixel_a(uint16_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current color of pixel on strip B
|
||||||
|
* @param index LED index
|
||||||
|
* @return RGB color
|
||||||
|
*/
|
||||||
|
rgb_t led_get_pixel_b(uint16_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add color to existing pixel (blending)
|
||||||
|
* @param index LED index on strip A
|
||||||
|
* @param color RGB color to add
|
||||||
|
*/
|
||||||
|
void led_add_pixel_a(uint16_t index, rgb_t color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add color to existing pixel (blending)
|
||||||
|
* @param index LED index on strip B
|
||||||
|
* @param color RGB color to add
|
||||||
|
*/
|
||||||
|
void led_add_pixel_b(uint16_t index, rgb_t color);
|
||||||
|
|
||||||
|
#endif // LED_H
|
||||||
208
main/localbtn.c
Normal file
208
main/localbtn.c
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
/**
|
||||||
|
* @file localbtn.c
|
||||||
|
* @brief Local GPIO button reading using interrupt-based edge detection
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "localbtn.h"
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const char *TAG = "LOCALBTN";
|
||||||
|
|
||||||
|
#define DEBOUNCE_TIME_MS 50 // Debounce time in milliseconds
|
||||||
|
|
||||||
|
// Button state
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
int8_t gpio_pin;
|
||||||
|
bool initialized;
|
||||||
|
TaskHandle_t task_handle;
|
||||||
|
QueueHandle_t event_queue;
|
||||||
|
localbtn_mode_change_callback_t callback;
|
||||||
|
int64_t last_press_time; // For debouncing
|
||||||
|
} button_state = {
|
||||||
|
.gpio_pin = -1,
|
||||||
|
.initialized = false,
|
||||||
|
.task_handle = NULL,
|
||||||
|
.event_queue = NULL,
|
||||||
|
.callback = NULL,
|
||||||
|
.last_press_time = 0};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GPIO interrupt handler (ISR)
|
||||||
|
* Minimal work in ISR - just send event to task
|
||||||
|
*/
|
||||||
|
static void IRAM_ATTR gpio_isr_handler(void *arg)
|
||||||
|
{
|
||||||
|
int64_t now = esp_timer_get_time();
|
||||||
|
|
||||||
|
// Send timestamp to queue for debouncing in task
|
||||||
|
BaseType_t high_priority_task_woken = pdFALSE;
|
||||||
|
xQueueSendFromISR(button_state.event_queue, &now, &high_priority_task_woken);
|
||||||
|
|
||||||
|
if (high_priority_task_woken)
|
||||||
|
{
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Button handling task
|
||||||
|
* Handles debouncing and callback execution
|
||||||
|
*/
|
||||||
|
static void localbtn_task(void *arg)
|
||||||
|
{
|
||||||
|
int64_t event_time;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Button task started, monitoring GPIO%d", button_state.gpio_pin);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// Wait for button press event from ISR
|
||||||
|
if (xQueueReceive(button_state.event_queue, &event_time, portMAX_DELAY))
|
||||||
|
{
|
||||||
|
// Debouncing: Check if enough time has passed since last press
|
||||||
|
int64_t time_since_last_press = (event_time - button_state.last_press_time) / 1000; // Convert to ms
|
||||||
|
|
||||||
|
if (time_since_last_press >= DEBOUNCE_TIME_MS)
|
||||||
|
{
|
||||||
|
// Valid button press - verify button is still pressed
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10)); // Small delay to ensure stable state
|
||||||
|
|
||||||
|
if (gpio_get_level(button_state.gpio_pin) == 0)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Button press detected on GPIO%d", button_state.gpio_pin);
|
||||||
|
|
||||||
|
button_state.last_press_time = event_time;
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (button_state.callback)
|
||||||
|
{
|
||||||
|
button_state.callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t localbtn_init(int8_t pin_localbtn)
|
||||||
|
{
|
||||||
|
if (pin_localbtn < 0)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Button disabled (invalid pin: %d)", pin_localbtn);
|
||||||
|
return ESP_ERR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button_state.initialized)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Button already initialized");
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
button_state.gpio_pin = pin_localbtn;
|
||||||
|
button_state.last_press_time = 0U;
|
||||||
|
|
||||||
|
// Create event queue for ISR->Task communication
|
||||||
|
button_state.event_queue = xQueueCreate(10, sizeof(int64_t));
|
||||||
|
if (button_state.event_queue == NULL)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to create event queue");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure GPIO
|
||||||
|
gpio_config_t io_conf = {
|
||||||
|
.pin_bit_mask = (1ULL << pin_localbtn),
|
||||||
|
.mode = GPIO_MODE_INPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_ENABLE, // Enable internal pull-up (safe even with external)
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_NEGEDGE // Interrupt on falling edge (button press)
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t ret = gpio_config(&io_conf);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
|
||||||
|
vQueueDelete(button_state.event_queue);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ISR handler for this GPIO
|
||||||
|
ret = gpio_isr_handler_add(pin_localbtn, gpio_isr_handler, NULL);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "ISR handler add failed: %s", esp_err_to_name(ret));
|
||||||
|
vQueueDelete(button_state.event_queue);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create button handling task
|
||||||
|
BaseType_t task_ret = xTaskCreate(
|
||||||
|
localbtn_task,
|
||||||
|
"localbtn_task",
|
||||||
|
2048,
|
||||||
|
NULL,
|
||||||
|
5, // Priority 5 (same as other tasks)
|
||||||
|
&button_state.task_handle);
|
||||||
|
|
||||||
|
if (task_ret != pdPASS)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to create button task");
|
||||||
|
gpio_isr_handler_remove(pin_localbtn);
|
||||||
|
vQueueDelete(button_state.event_queue);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
button_state.initialized = true;
|
||||||
|
ESP_LOGI(TAG, "Button initialized on GPIO%d with interrupt-based detection", pin_localbtn);
|
||||||
|
ESP_LOGI(TAG, "Debounce time: %d ms", DEBOUNCE_TIME_MS);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void localbtn_deinit(void)
|
||||||
|
{
|
||||||
|
if (!button_state.initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove ISR handler
|
||||||
|
if (button_state.gpio_pin >= 0)
|
||||||
|
{
|
||||||
|
gpio_isr_handler_remove(button_state.gpio_pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete task
|
||||||
|
if (button_state.task_handle)
|
||||||
|
{
|
||||||
|
vTaskDelete(button_state.task_handle);
|
||||||
|
button_state.task_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete queue
|
||||||
|
if (button_state.event_queue)
|
||||||
|
{
|
||||||
|
vQueueDelete(button_state.event_queue);
|
||||||
|
button_state.event_queue = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
button_state.initialized = false;
|
||||||
|
button_state.callback = NULL;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Button deinitialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void localbtn_register_callback(localbtn_mode_change_callback_t cb)
|
||||||
|
{
|
||||||
|
button_state.callback = cb;
|
||||||
|
ESP_LOGI(TAG, "Callback registered");
|
||||||
|
}
|
||||||
37
main/localbtn.h
Normal file
37
main/localbtn.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* @file localbtn.h
|
||||||
|
* @brief Local GPIO button reading using interrupt-based edge detection
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LOCALBTN_H
|
||||||
|
#define LOCALBTN_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback function type for mode changes
|
||||||
|
*/
|
||||||
|
typedef void (*localbtn_mode_change_callback_t)();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize local button with interrupt-based detection
|
||||||
|
* @param pin_localbtn GPIO pin number for button (active low)
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t localbtn_init(int8_t pin_localbtn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deinitialize local button reading
|
||||||
|
*/
|
||||||
|
void localbtn_deinit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register callback for mode changes
|
||||||
|
* @param cb Callback function
|
||||||
|
*/
|
||||||
|
void localbtn_register_callback(localbtn_mode_change_callback_t cb);
|
||||||
|
|
||||||
|
#endif // LOCALBTN_H
|
||||||
91
main/main.c
Normal file
91
main/main.c
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* @file main.c
|
||||||
|
* @brief Main application entry point for LED Controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "animation.h"
|
||||||
|
#include "led.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static const char *TAG = "MAIN";
|
||||||
|
|
||||||
|
#define ANIMATION_UPDATE_RATE_MS 16 // ~60 FPS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Animation update task
|
||||||
|
* Runs continuously to update LED animations
|
||||||
|
*/
|
||||||
|
static void animation_task(void *pvParameters)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Animation task started");
|
||||||
|
|
||||||
|
TickType_t last_wake_time = xTaskGetTickCount();
|
||||||
|
const TickType_t update_interval = pdMS_TO_TICKS(ANIMATION_UPDATE_RATE_MS);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
animation_update();
|
||||||
|
vTaskDelayUntil(&last_wake_time, update_interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Main application entry point
|
||||||
|
*/
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "==============================================");
|
||||||
|
ESP_LOGI(TAG, " ESP32 LED Controller for Model Aircraft");
|
||||||
|
ESP_LOGI(TAG, "==============================================");
|
||||||
|
|
||||||
|
// Initialize control system (LEDs, PWM)
|
||||||
|
esp_err_t ret = control_init();
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to initialize control system: %s", esp_err_to_name(ret));
|
||||||
|
ESP_LOGE(TAG, "System halted. Please reset the device.");
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create animation update task
|
||||||
|
BaseType_t task_ret = xTaskCreate(
|
||||||
|
animation_task,
|
||||||
|
"animation",
|
||||||
|
4096,
|
||||||
|
NULL,
|
||||||
|
5,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
if (task_ret != pdPASS)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to create animation task");
|
||||||
|
ESP_LOGE(TAG, "System halted. Please reset the device.");
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_set_mode((animation_mode_t)control_get_animation_mode());
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "System initialized successfully");
|
||||||
|
|
||||||
|
// Main loop - just monitor system status
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||||
|
|
||||||
|
// Periodic status logging
|
||||||
|
ESP_LOGI(TAG, "Animation Mode set to: %s", animation_get_mode_name(control_get_animation_mode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
187
main/rcsignal.c
Normal file
187
main/rcsignal.c
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/**
|
||||||
|
* @file rcsignal.c
|
||||||
|
* @brief RC PWM signal reading implementation using edge capture
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rcsignal.h"
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const char *TAG = "RCSIGNAL";
|
||||||
|
|
||||||
|
#define PULSE_THRESHOLD_US 1500
|
||||||
|
#define SIGNAL_TIMEOUT_MS 100
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
int8_t gpio_pin;
|
||||||
|
volatile uint32_t pulse_width_us;
|
||||||
|
volatile int64_t last_edge_time;
|
||||||
|
volatile int64_t pulse_start_time;
|
||||||
|
volatile bool last_level;
|
||||||
|
volatile bool signal_active;
|
||||||
|
volatile bool pull_detected;
|
||||||
|
uint8_t current_mode;
|
||||||
|
rcsignal_mode_change_callback_t callback;
|
||||||
|
bool initialized;
|
||||||
|
TaskHandle_t monitor_task;
|
||||||
|
} rcsignal = {
|
||||||
|
.gpio_pin = -1,
|
||||||
|
.pulse_width_us = 0,
|
||||||
|
.last_edge_time = 0,
|
||||||
|
.pulse_start_time = 0,
|
||||||
|
.last_level = false,
|
||||||
|
.signal_active = false,
|
||||||
|
.pull_detected = false,
|
||||||
|
.current_mode = 0,
|
||||||
|
.callback = NULL,
|
||||||
|
.initialized = false,
|
||||||
|
.monitor_task = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void IRAM_ATTR gpio_isr_handler(void *arg)
|
||||||
|
{
|
||||||
|
int64_t now = esp_timer_get_time();
|
||||||
|
bool level = gpio_get_level(rcsignal.gpio_pin);
|
||||||
|
|
||||||
|
if (level && !rcsignal.last_level)
|
||||||
|
{
|
||||||
|
// Rising edge - start of pulse
|
||||||
|
rcsignal.pulse_start_time = now;
|
||||||
|
}
|
||||||
|
else if (!level && rcsignal.last_level)
|
||||||
|
{
|
||||||
|
// Falling edge - end of pulse
|
||||||
|
if (rcsignal.pulse_start_time > 0)
|
||||||
|
{
|
||||||
|
rcsignal.pulse_width_us = (uint32_t)(now - rcsignal.pulse_start_time);
|
||||||
|
rcsignal.last_edge_time = now;
|
||||||
|
rcsignal.signal_active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rcsignal.last_level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void monitor_task(void *arg)
|
||||||
|
{
|
||||||
|
uint32_t last_pulse_width = 0;
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
|
||||||
|
// Check for signal timeout
|
||||||
|
int64_t now = esp_timer_get_time();
|
||||||
|
if (rcsignal.signal_active && (now - rcsignal.last_edge_time) > (SIGNAL_TIMEOUT_MS * 1000))
|
||||||
|
{
|
||||||
|
rcsignal.signal_active = false;
|
||||||
|
rcsignal.pulse_width_us = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect mode change (rising edge on PWM signal > 1500us)
|
||||||
|
if (rcsignal.pulse_width_us != last_pulse_width)
|
||||||
|
{
|
||||||
|
last_pulse_width = rcsignal.pulse_width_us;
|
||||||
|
|
||||||
|
if (rcsignal.pulse_width_us < PULSE_THRESHOLD_US)
|
||||||
|
{
|
||||||
|
rcsignal.pull_detected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rcsignal.pulse_width_us > PULSE_THRESHOLD_US && rcsignal.pull_detected)
|
||||||
|
{
|
||||||
|
// Mode change detected
|
||||||
|
rcsignal.pull_detected = false;
|
||||||
|
|
||||||
|
if (rcsignal.callback)
|
||||||
|
{
|
||||||
|
rcsignal.callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rcsignal_init(int8_t pin)
|
||||||
|
{
|
||||||
|
if (pin < 0)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "RC signal disabled (no pin configured)");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcsignal.gpio_pin = pin;
|
||||||
|
|
||||||
|
// Configure GPIO
|
||||||
|
gpio_config_t io_conf = {
|
||||||
|
.pin_bit_mask = (1ULL << pin),
|
||||||
|
.mode = GPIO_MODE_INPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_ANYEDGE,
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||||
|
|
||||||
|
// Install ISR service
|
||||||
|
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(pin, gpio_isr_handler, NULL));
|
||||||
|
|
||||||
|
// Create monitor task
|
||||||
|
BaseType_t ret = xTaskCreate(monitor_task, "rcsignal_monitor", 2048, NULL, 5, &rcsignal.monitor_task);
|
||||||
|
if (ret != pdPASS)
|
||||||
|
{
|
||||||
|
gpio_isr_handler_remove(pin);
|
||||||
|
gpio_uninstall_isr_service();
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcsignal.initialized = true;
|
||||||
|
ESP_LOGI(TAG, "RC signal initialized on GPIO%d", pin);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcsignal_deinit(void)
|
||||||
|
{
|
||||||
|
if (!rcsignal.initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (rcsignal.monitor_task)
|
||||||
|
{
|
||||||
|
vTaskDelete(rcsignal.monitor_task);
|
||||||
|
rcsignal.monitor_task = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rcsignal.gpio_pin >= 0)
|
||||||
|
{
|
||||||
|
gpio_isr_handler_remove(rcsignal.gpio_pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
rcsignal.initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcsignal_register_callback(rcsignal_mode_change_callback_t callback)
|
||||||
|
{
|
||||||
|
rcsignal.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rcsignal_get_pulse_width(void)
|
||||||
|
{
|
||||||
|
return rcsignal.pulse_width_us;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rcsignal_is_active(void)
|
||||||
|
{
|
||||||
|
return rcsignal.signal_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rcsignal_get_current_mode(void)
|
||||||
|
{
|
||||||
|
return rcsignal.current_mode;
|
||||||
|
}
|
||||||
55
main/rcsignal.h
Normal file
55
main/rcsignal.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* @file rcsignal.h
|
||||||
|
* @brief RC PWM signal reading and parsing module
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RCSIGNAL_H
|
||||||
|
#define RCSIGNAL_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback function type for mode changes
|
||||||
|
*/
|
||||||
|
typedef void (*rcsignal_mode_change_callback_t)();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize RC signal reading
|
||||||
|
* @param pin GPIO pin for PWM input (-1 to disable)
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t rcsignal_init(int8_t pin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deinitialize RC signal reading
|
||||||
|
*/
|
||||||
|
void rcsignal_deinit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register callback for mode changes
|
||||||
|
* @param callback Callback function
|
||||||
|
*/
|
||||||
|
void rcsignal_register_callback(rcsignal_mode_change_callback_t callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current PWM pulse width in microseconds
|
||||||
|
* @return Pulse width in µs (0 if no signal)
|
||||||
|
*/
|
||||||
|
uint32_t rcsignal_get_pulse_width(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if PWM signal is active
|
||||||
|
* @return true if signal detected in last 100ms
|
||||||
|
*/
|
||||||
|
bool rcsignal_is_active(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current mode
|
||||||
|
* @return Current animation mode (0-13)
|
||||||
|
*/
|
||||||
|
uint8_t rcsignal_get_current_mode(void);
|
||||||
|
|
||||||
|
#endif // RCSIGNAL_H
|
||||||
85
nfc01.ino
85
nfc01.ino
@ -1,85 +0,0 @@
|
|||||||
#include "FastLED.h"
|
|
||||||
|
|
||||||
int rc01 = 9;
|
|
||||||
int rc02 = 10;
|
|
||||||
int led_spotlight = 2;
|
|
||||||
int rc01Val = 0;
|
|
||||||
int rc02Val = 0;
|
|
||||||
int modus = 0;
|
|
||||||
int modusMax = 13;
|
|
||||||
int red = 0;
|
|
||||||
int green = 0;
|
|
||||||
int blue = 0;
|
|
||||||
int randomVal = 0;
|
|
||||||
|
|
||||||
#define DATA_PIN 3
|
|
||||||
#define LED_TYPE WS2812B
|
|
||||||
#define COLOR_ORDER GRB
|
|
||||||
#define NUM_LEDS 44
|
|
||||||
CRGB leds[NUM_LEDS];
|
|
||||||
#define BRIGHTNESS 255
|
|
||||||
#define FRAMES_PER_SECOND 60
|
|
||||||
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
|
|
||||||
|
|
||||||
boolean pullRC = true;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
//Serial.begin(9600);
|
|
||||||
Serial.println("_-_-_- Night Fly Controller V01 _-_-_-");
|
|
||||||
pinMode(rc01, INPUT);
|
|
||||||
pinMode(rc02, INPUT);
|
|
||||||
pinMode(led_spotlight, OUTPUT);
|
|
||||||
pinMode(LED_BUILTIN, OUTPUT);
|
|
||||||
digitalWrite(LED_BUILTIN, HIGH);
|
|
||||||
|
|
||||||
delay(3000); // 3 second delay for recovery
|
|
||||||
|
|
||||||
// tell FastLED about the LED strip configuration
|
|
||||||
FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
|
|
||||||
// set master brightness control
|
|
||||||
FastLED.setBrightness(BRIGHTNESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// List of patterns to cycle through. Each is defined as a separate function below.
|
|
||||||
typedef void (*SimplePatternList[])();
|
|
||||||
SimplePatternList gPatterns = { blackMode, redMode, blueMode, greenMode, whiteMode, rainbow, rainbowWithGlitter, confetti, sinelon, bpm, navigation, chase, chaseRGB, randomMode };
|
|
||||||
uint8_t gHue = 0; // rotating "base color" used by many of the patterns
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
|
|
||||||
if (getRC01()) {
|
|
||||||
digitalWrite(led_spotlight, HIGH);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
digitalWrite(led_spotlight, LOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
setModus();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void setModus(){
|
|
||||||
if (getRC02()) {
|
|
||||||
modus = modus + 1;
|
|
||||||
if (modus > modusMax) {
|
|
||||||
modus = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Serial.println(modus);
|
|
||||||
serialPrintModus(modus);
|
|
||||||
|
|
||||||
gPatterns[modus]();
|
|
||||||
|
|
||||||
// send the 'leds' array out to the actual LED strip
|
|
||||||
FastLED.show();
|
|
||||||
// insert a delay to keep the framerate modest
|
|
||||||
FastLED.delay(1000 / FRAMES_PER_SECOND);
|
|
||||||
|
|
||||||
// do some periodic updates
|
|
||||||
EVERY_N_MILLISECONDS( 20 ) {
|
|
||||||
gHue++; // slowly cycle the "base color" through the rainbow
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
6
partitions.csv
Normal file
6
partitions.csv
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
# Optimized for 2MB flash - 2 large OTA slots (no factory partition)
|
||||||
|
nvs, data, nvs, 0x9000, 0x4000,
|
||||||
|
phy_init, data, phy, 0xd000, 0x1000,
|
||||||
|
ota_0, app, ota_0, 0x10000, 0xF0000,
|
||||||
|
ota_1, app, ota_1, 0x100000, 0xF0000,
|
||||||
|
Reference in New Issue
Block a user