More optimize
This commit is contained in:
+15
-12
@@ -22,10 +22,10 @@ On every `loop()` call (~60 times per second):
|
|||||||
|
|
||||||
1. Check the button for events; switch show if needed (`load_show()`).
|
1. Check the button for events; switch show if needed (`load_show()`).
|
||||||
2. Read the current step from `SHOW[].steps` in PROGMEM.
|
2. Read the current step from `SHOW[].steps` in PROGMEM.
|
||||||
3. Compute progress `t = elapsed / duration_ms`, clamped to 0.0–1.0.
|
3. Compute progress `t` (0–255) as `elapsed * 255 / duration_ms`, clamped.
|
||||||
4. Blend `s_from_color` toward the target color by `t`.
|
4. Blend `s_from_color` toward the target color using FastLED's `blend()`.
|
||||||
5. Push the blended color to the LEDs.
|
5. Push the blended color to the LEDs.
|
||||||
6. If `t >= 1.0`, advance to the next step (loops at end of show).
|
6. If `t == 255`, advance to the next step. For `SHOW_LOOP` shows this wraps; for `SHOW_SINGLE` shows this loads the next show.
|
||||||
|
|
||||||
To **hold** a color, add two steps with the same color — the second transition is from the color to itself, which is invisible.
|
To **hold** a color, add two steps with the same color — the second transition is from the color to itself, which is invisible.
|
||||||
|
|
||||||
@@ -33,23 +33,26 @@ To **hold** a color, add two steps with the same color — the second transition
|
|||||||
|
|
||||||
The Arduino Uno has only 2KB of RAM. Storing all show data in normal arrays would exhaust it quickly. `PROGMEM` stores data in flash memory (32KB), which the Uno has much more of. Both the `Step` arrays and the `ShowDef` index (`SHOWS[]`) live in PROGMEM; the `read_step()` and `read_show_def()` helpers in `lightshow_format.h` extract values safely.
|
The Arduino Uno has only 2KB of RAM. Storing all show data in normal arrays would exhaust it quickly. `PROGMEM` stores data in flash memory (32KB), which the Uno has much more of. Both the `Step` arrays and the `ShowDef` index (`SHOWS[]`) live in PROGMEM; the `read_step()` and `read_show_def()` helpers in `lightshow_format.h` extract values safely.
|
||||||
|
|
||||||
Each `Step` is 5 bytes (3 bytes RGB + 2 bytes duration). A `ShowDef` is 4 bytes (2-byte pointer + 2-byte length). The sketch currently uses 21% of flash — there is room for many more shows.
|
Each `Step` is 5 bytes (3 bytes RGB + 2 bytes duration). A `ShowDef` is 6 bytes (2-byte pointer + 2-byte length + 1-byte mode + 1 byte padding). The sketch currently uses roughly 25% of flash — there is room for many more shows.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How the button works
|
## How the button works
|
||||||
|
|
||||||
`button.h/.cpp` implements a state machine with software debouncing:
|
Button handling uses the [OneButton](https://github.com/mathertel/OneButton) library. A `OneButton` instance is created in `cosplay_lights.ino` with callbacks attached in `setup()`:
|
||||||
|
|
||||||
1. **Debounce** — the raw pin state must be stable for `DEBOUNCE_MS` (50ms) before the debounced state updates. This filters contact bounce.
|
- **Single tap** → next show
|
||||||
|
- **Double tap** → previous show
|
||||||
|
- **Long press** → reset to show 0
|
||||||
|
|
||||||
2. **Hold detection** — if the debounced state stays pressed for `HOLD_MS` (800ms), `BTN_HOLD` fires immediately (doesn't wait for release). Any pending tap count is discarded.
|
The key timing values (configured via `setClickMs` and `setPressMs`):
|
||||||
|
|
||||||
3. **Tap counting** — each clean release increments a tap counter and records the release time.
|
| Setting | Value | Effect |
|
||||||
|
|---------|-------|--------|
|
||||||
|
| `setClickMs(400)` | 400ms | Single tap confirmed after 400ms of silence (to distinguish from a double tap) |
|
||||||
|
| `setPressMs(800)` | 800ms | Hold duration before long press fires |
|
||||||
|
|
||||||
4. **Tap resolution** — after `DOUBLE_TAP_MS` (400ms) of silence, the accumulated tap count is resolved: 1 tap → `BTN_TAP`, 2+ taps → `BTN_DOUBLE_TAP`. This introduces a 400ms delay on single taps (acceptable for show navigation).
|
The button is polled every `loop()` iteration (not just once per frame), so it responds immediately even during the 16ms LED update window. OneButton handles debouncing internally.
|
||||||
|
|
||||||
Timing constants are at the top of `button.cpp` and can be adjusted.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -59,7 +62,7 @@ Timing constants are at the top of `button.cpp` and can be adjusted.
|
|||||||
|
|
||||||
The generation pipeline:
|
The generation pipeline:
|
||||||
1. Reads every `.txt` in `converter/shows/`.
|
1. Reads every `.txt` in `converter/shows/`.
|
||||||
2. Converts `blue_pulse.txt` first (always index 0), then all others alphabetically.
|
2. Converts `blue_breath.txt` first (always index 0), then all others alphabetically.
|
||||||
3. Writes one `show_<name>.h` per file into `arduino/cosplay_lights/`.
|
3. Writes one `show_<name>.h` per file into `arduino/cosplay_lights/`.
|
||||||
4. Regenerates `shows.h` with updated `#include` lines and `SHOWS[]` table.
|
4. Regenerates `shows.h` with updated `#include` lines and `SHOWS[]` table.
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ This file records the original design brief and decisions so future contributors
|
|||||||
2. **Multi-show navigation** — all shows are compiled into the firmware. A single button lets you cycle through them:
|
2. **Multi-show navigation** — all shows are compiled into the firmware. A single button lets you cycle through them:
|
||||||
- 1 tap — next show
|
- 1 tap — next show
|
||||||
- 2 taps — previous show
|
- 2 taps — previous show
|
||||||
- Hold — reset to show 0 (slow blue pulse)
|
- Hold — reset to show 0 (blue breath)
|
||||||
|
|
||||||
3. **Arduino + WS2812B** — runs on an Arduino Uno with a WS2812B (NeoPixel) LED strip.
|
3. **Arduino + WS2812B** — runs on an Arduino Uno with a WS2812B (NeoPixel) LED strip.
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ Write/edit .txt files in converter/shows/
|
|||||||
|
|
||||||
## Future ideas (not yet implemented)
|
## Future ideas (not yet implemented)
|
||||||
|
|
||||||
- Per-LED patterns (chase, sparkle, gradient segments)
|
- Per-LED patterns — chase, sparkle (random white flashes over a base color), gradient segments. The `led_controller.cpp` pattern system already has the hook: add a `#define PATTERN_SPARKLE` in `config.h` and implement the branch in `leds_apply_color()`
|
||||||
- Multiple strip support
|
- Multiple strip support
|
||||||
- Battery power optimization (auto-dim, sleep)
|
- Battery power optimization (auto-dim, sleep)
|
||||||
- SD card loading (no recompile needed for new shows)
|
- SD card loading (no recompile needed for new shows)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ Uses the internal pull-up resistor. No external resistor needed. Pin 2 can be ch
|
|||||||
|-------|--------|
|
|-------|--------|
|
||||||
| 1 tap | Next show |
|
| 1 tap | Next show |
|
||||||
| 2 taps | Previous show |
|
| 2 taps | Previous show |
|
||||||
| Hold (~0.8s) | Reset to show 0 (slow blue pulse) |
|
| Hold (~0.8s) | Reset to show 0 (blue breath) |
|
||||||
|
|
||||||
### Power warning — read this
|
### Power warning — read this
|
||||||
|
|
||||||
@@ -70,9 +70,12 @@ A 60-LED strip at full white = **3.6A** — far more than the Arduino's 5V pin c
|
|||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
### 1. Install the FastLED library
|
### 1. Install required libraries
|
||||||
|
|
||||||
Arduino IDE: **Sketch → Include Library → Manage Libraries** → search `FastLED` → Install.
|
Arduino IDE: **Sketch → Include Library → Manage Libraries**, then install both:
|
||||||
|
|
||||||
|
- **FastLED** — LED strip control and color math
|
||||||
|
- **OneButton** — button debounce and gesture detection
|
||||||
|
|
||||||
### 2. Configure your hardware
|
### 2. Configure your hardware
|
||||||
|
|
||||||
@@ -84,14 +87,28 @@ Edit `arduino/cosplay_lights/config.h`:
|
|||||||
|
|
||||||
### 3. Write or edit show files
|
### 3. Write or edit show files
|
||||||
|
|
||||||
Show files live in `converter/shows/`. Four are included:
|
Show files live in `converter/shows/`. These are included:
|
||||||
|
|
||||||
| File | Description |
|
| File | Description | Mode |
|
||||||
|------|-------------|
|
|------|-------------|------|
|
||||||
| `blue_pulse.txt` | Slow blue breathing — always show 0 (home/reset) |
|
| `blue_breath.txt` | Slow blue breathing — always show 0 (home/reset) | loop |
|
||||||
| `example_fade.txt` | Slow color cycle |
|
| `001_heartbeat.txt` | 3 heartbeats in bright red, then auto-advances to breathing | single |
|
||||||
| `example_party.txt` | Fast rainbow flashes |
|
| `002_breath_red.txt` | Breathing between dark maroon and bright red, 5s cycle | loop |
|
||||||
| `example_pulse.txt` | Red heartbeat |
|
| `003_solid_red.txt` | Constant deep red | loop |
|
||||||
|
| `example_fade.txt` | Slow color cycle | loop |
|
||||||
|
| `example_party.txt` | Fast rainbow flashes | loop |
|
||||||
|
| `example_pulse.txt` | Red heartbeat | loop |
|
||||||
|
|
||||||
|
**Show modes:**
|
||||||
|
|
||||||
|
- `loop` — repeats indefinitely until the button is pressed
|
||||||
|
- `single` — plays once, then automatically advances to the next show
|
||||||
|
|
||||||
|
Set the mode with a comment anywhere in the `.txt` file:
|
||||||
|
```
|
||||||
|
// mode: single
|
||||||
|
```
|
||||||
|
If no mode line is present, the show loops.
|
||||||
|
|
||||||
**Show file format:**
|
**Show file format:**
|
||||||
|
|
||||||
@@ -133,7 +150,21 @@ make upload PORT=/dev/ttyUSB1 # target a specific port
|
|||||||
1. Create a `.txt` file in `converter/shows/` using the format above.
|
1. Create a `.txt` file in `converter/shows/` using the format above.
|
||||||
2. Run `make upload` — your new show is automatically included.
|
2. Run `make upload` — your new show is automatically included.
|
||||||
|
|
||||||
The order in which shows appear when cycling through with the button is: `blue_pulse` first, then all others sorted alphabetically by filename.
|
The order in which shows appear when cycling through with the button is: `blue_breath` first (always show 0), then all others sorted alphabetically by filename. Prefix filenames with numbers (`001_`, `002_`, …) to control order.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File overview
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Wokwi](https://wokwi.com/projects/new/arduino-uno) — browser-based Arduino + LED strip emulator, useful for testing shows without hardware
|
||||||
|
- [tweaking4all — LED strip effects](https://www.tweaking4all.nl/hardware/arduino/adruino-led-strip-effecten/) — reference for animation patterns (sparkle, rainbow, etc.)
|
||||||
|
- [FastLED color fills reference](https://fastled.io/docs/da/de3/group___color_fills.html)
|
||||||
|
- [Adafruit LED animation guide](https://learn.adafruit.com/circuitpython-led-animations/basic-animations)
|
||||||
|
- [htmlcolorcodes.com](https://htmlcolorcodes.com/) — hex color picker for choosing show colors
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -144,7 +175,6 @@ Amirine_Cosplay_Lights/
|
|||||||
├── arduino/cosplay_lights/
|
├── arduino/cosplay_lights/
|
||||||
│ ├── cosplay_lights.ino — main sketch (upload this)
|
│ ├── cosplay_lights.ino — main sketch (upload this)
|
||||||
│ ├── config.h — hardware settings
|
│ ├── config.h — hardware settings
|
||||||
│ ├── button.h / .cpp — button debounce and event detection
|
|
||||||
│ ├── led_controller.h / .cpp — LED abstraction layer
|
│ ├── led_controller.h / .cpp — LED abstraction layer
|
||||||
│ ├── lightshow_format.h — Step and ShowDef structs
|
│ ├── lightshow_format.h — Step and ShowDef structs
|
||||||
│ ├── shows.h — master show index (regenerated by make shows)
|
│ ├── shows.h — master show index (regenerated by make shows)
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Generated by convert_all.py from: 001_heartbeat.txt
|
||||||
|
// Do not edit manually — edit the .txt file and run: make shows
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "lightshow_format.h"
|
||||||
|
|
||||||
|
const Step SHOW_001_HEARTBEAT[] PROGMEM = {
|
||||||
|
{ 5, 0, 0, 0}, // #050000, 0ms
|
||||||
|
{209, 14, 41, 120}, // #D10E29, 120ms
|
||||||
|
{ 48, 0, 0, 100}, // #300000, 100ms
|
||||||
|
{209, 14, 41, 100}, // #D10E29, 100ms
|
||||||
|
{ 5, 0, 0, 700}, // #050000, 700ms
|
||||||
|
{209, 14, 41, 120}, // #D10E29, 120ms
|
||||||
|
{ 48, 0, 0, 100}, // #300000, 100ms
|
||||||
|
{209, 14, 41, 100}, // #D10E29, 100ms
|
||||||
|
{ 5, 0, 0, 700}, // #050000, 700ms
|
||||||
|
{209, 14, 41, 120}, // #D10E29, 120ms
|
||||||
|
{ 48, 0, 0, 100}, // #300000, 100ms
|
||||||
|
{209, 14, 41, 100}, // #D10E29, 100ms
|
||||||
|
{ 5, 0, 0, 700}, // #050000, 700ms
|
||||||
|
{153, 40, 58, 2000}, // #99283A, 2000ms
|
||||||
|
};
|
||||||
|
const uint16_t SHOW_001_HEARTBEAT_LENGTH = 14;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Generated by convert_all.py from: 002_breath_red.txt
|
||||||
|
// Do not edit manually — edit the .txt file and run: make shows
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "lightshow_format.h"
|
||||||
|
|
||||||
|
const Step SHOW_002_BREATH_RED[] PROGMEM = {
|
||||||
|
{153, 40, 58, 0}, // #99283A, 0ms
|
||||||
|
{217, 30, 30, 2500}, // #D91E1E, 2500ms
|
||||||
|
{153, 40, 58, 2500}, // #99283A, 2500ms
|
||||||
|
};
|
||||||
|
const uint16_t SHOW_002_BREATH_RED_LENGTH = 3;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Generated by convert_all.py from: 003_solid_red.txt
|
||||||
|
// Do not edit manually — edit the .txt file and run: make shows
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "lightshow_format.h"
|
||||||
|
|
||||||
|
const Step SHOW_003_SOLID_RED[] PROGMEM = {
|
||||||
|
{153, 40, 58, 0}, // #99283A, 0ms
|
||||||
|
{153, 40, 58, 30000}, // #99283A, 30000ms
|
||||||
|
};
|
||||||
|
const uint16_t SHOW_003_SOLID_RED_LENGTH = 2;
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
|
|
||||||
// ---- Individual show data ----------------------------------------------
|
// ---- Individual show data ----------------------------------------------
|
||||||
#include "show_blue_breath.h"
|
#include "show_blue_breath.h"
|
||||||
|
#include "show_001_heartbeat.h"
|
||||||
|
#include "show_002_breath_red.h"
|
||||||
|
#include "show_003_solid_red.h"
|
||||||
#include "show_example_fade.h"
|
#include "show_example_fade.h"
|
||||||
#include "show_example_party.h"
|
#include "show_example_party.h"
|
||||||
#include "show_example_pulse.h"
|
#include "show_example_pulse.h"
|
||||||
@@ -21,9 +24,12 @@
|
|||||||
// ---- Show index (PROGMEM) ----------------------------------------------
|
// ---- Show index (PROGMEM) ----------------------------------------------
|
||||||
const ShowDef SHOWS[] PROGMEM = {
|
const ShowDef SHOWS[] PROGMEM = {
|
||||||
{SHOW_BLUE_BREATH, SHOW_BLUE_BREATH_LENGTH, SHOW_LOOP}, // 0 — home show
|
{SHOW_BLUE_BREATH, SHOW_BLUE_BREATH_LENGTH, SHOW_LOOP}, // 0 — home show
|
||||||
{SHOW_EXAMPLE_FADE, SHOW_EXAMPLE_FADE_LENGTH, SHOW_LOOP}, // 1
|
{SHOW_001_HEARTBEAT, SHOW_001_HEARTBEAT_LENGTH, SHOW_SINGLE}, // 1
|
||||||
{SHOW_EXAMPLE_PARTY, SHOW_EXAMPLE_PARTY_LENGTH, SHOW_LOOP}, // 2
|
{SHOW_002_BREATH_RED, SHOW_002_BREATH_RED_LENGTH, SHOW_LOOP}, // 2
|
||||||
{SHOW_EXAMPLE_PULSE, SHOW_EXAMPLE_PULSE_LENGTH, SHOW_LOOP}, // 3
|
{SHOW_003_SOLID_RED, SHOW_003_SOLID_RED_LENGTH, SHOW_LOOP}, // 3
|
||||||
|
{SHOW_EXAMPLE_FADE, SHOW_EXAMPLE_FADE_LENGTH, SHOW_LOOP}, // 4
|
||||||
|
{SHOW_EXAMPLE_PARTY, SHOW_EXAMPLE_PARTY_LENGTH, SHOW_LOOP}, // 5
|
||||||
|
{SHOW_EXAMPLE_PULSE, SHOW_EXAMPLE_PULSE_LENGTH, SHOW_LOOP}, // 6
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint8_t SHOW_COUNT = 4;
|
const uint8_t SHOW_COUNT = 7;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Monster Hunter cosplay — heartbeat intro
|
||||||
|
// Three heartbeats in bright red, then fades into the base color of the breathing show.
|
||||||
|
// Auto-advances to 002_breath_red when done.
|
||||||
|
//
|
||||||
|
// mode: single
|
||||||
|
// Format: #RRGGBB, duration_ms
|
||||||
|
|
||||||
|
#050000, 0 // start near black
|
||||||
|
|
||||||
|
// beat 1
|
||||||
|
#d10e29, 120 // lub — fast rise to bright red
|
||||||
|
#300000, 100 // dip between beats
|
||||||
|
#d10e29, 100 // dub
|
||||||
|
#050000, 700 // rest
|
||||||
|
|
||||||
|
// beat 2
|
||||||
|
#d10e29, 120 // lub
|
||||||
|
#300000, 100 // dip
|
||||||
|
#d10e29, 100 // dub
|
||||||
|
#050000, 700 // rest
|
||||||
|
|
||||||
|
// beat 3
|
||||||
|
#d10e29, 120 // lub
|
||||||
|
#300000, 100 // dip
|
||||||
|
#d10e29, 100 // dub
|
||||||
|
#050000, 700 // rest
|
||||||
|
|
||||||
|
// 2-second fade into the base color of the breathing show
|
||||||
|
#99283a, 2000
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Monster Hunter cosplay — breathing red
|
||||||
|
// Slow breathing between dark maroon (#99283a) and bright red (#d91e1e).
|
||||||
|
// One breath cycle lasts 5 seconds. Loops continuously.
|
||||||
|
//
|
||||||
|
// mode: loop
|
||||||
|
// Format: #RRGGBB, duration_ms
|
||||||
|
|
||||||
|
#99283a, 0 // snap to dim base color
|
||||||
|
#d91e1e, 2500 // breathe in — slow rise to bright red
|
||||||
|
#99283a, 2500 // breathe out — slow fall back to dim
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Monster Hunter cosplay — solid deep red
|
||||||
|
// Constant dark maroon (#99283a). Stays on until the button is pressed.
|
||||||
|
//
|
||||||
|
// mode: loop
|
||||||
|
// Format: #RRGGBB, duration_ms
|
||||||
|
|
||||||
|
#99283a, 0 // snap on instantly
|
||||||
|
#99283a, 30000 // hold (30s loop is invisible — same color each cycle)
|
||||||
Reference in New Issue
Block a user