Files
bgrolleman a96f378c9c Add triple-tap lights-off and fix LED pin to D5
- Triple tap turns off LEDs; any subsequent tap or hold resumes the current show
- Change LED_DATA_PIN default from 6 to 5 across config, docs, and wiring diagram
- Fix Makefile upload to pass --input-dir so it uses the pre-built binary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:20:36 +02:00

159 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture Details — Amirine Cosplay Lights
For anyone who wants to understand or modify how the project works.
SPDX-License-Identifier: BSD-2-Clause
---
## How show playback works
The Arduino sketch maintains a small amount of playback state:
| Variable | Type | What it stores |
|---|---|---|
| `s_show_index` | `uint8_t` | Which show is active (index into `SHOWS[]`) |
| `s_show` | `ShowDef` | Active show descriptor (read from PROGMEM once on load) |
| `s_step_index` | `uint16_t` | Current step within the active show |
| `s_step_start` | `uint32_t` | `millis()` when the current step began |
| `s_from_color` | `CRGB` | Color the strip was showing when this step started |
On every `loop()` call (~60 times per second):
1. Check the button for events; switch show if needed (`load_show()`).
2. Read the current step from `SHOW[].steps` in PROGMEM.
3. Compute progress `t` (0255) as `elapsed * 255 / duration_ms`, clamped.
4. Blend `s_from_color` toward the target color using FastLED's `blend()`.
5. Push the blended color to the LEDs.
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.
### Why PROGMEM?
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 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
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()`:
- **Single tap** → next show
- **Double tap** → previous show
- **Triple tap** → lights off; any subsequent tap or hold resumes the current show
- **Long press** → reset to show 0
The key timing values (configured via `setClickMs` and `setPressMs`):
| 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 |
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.
---
## File generation
`shows.h` and `show_*.h` are **generated files** — they are the output of `converter/convert_all.py`. The source of truth is the `.txt` files in `converter/shows/`.
The generation pipeline:
1. Reads every `.txt` in `converter/shows/`.
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/`.
4. Regenerates `shows.h` with updated `#include` lines and `SHOWS[]` table.
Running `make shows` triggers this. `make upload` runs `make shows` first automatically.
---
## Adding a new LED pattern
The current `ACTIVE_PATTERN` is `PATTERN_SOLID` — all LEDs show the same blended color.
To add, for example, a chase pattern:
**Step 1** — Add a `#define` in `config.h`:
```cpp
#define PATTERN_SOLID 0
#define PATTERN_CHASE 1
#define ACTIVE_PATTERN PATTERN_CHASE
```
**Step 2** — Add the pattern logic in `led_controller.cpp`:
```cpp
#elif ACTIVE_PATTERN == PATTERN_CHASE
// One lit LED chases along the strip using the blended show color.
static uint16_t head = 0;
fill_solid(leds, NUM_LEDS, CRGB::Black);
leds[head] = color;
head = (head + 1) % NUM_LEDS;
```
The `color` parameter is always the show's current blended color, so patterns automatically follow lightshow transitions.
---
## Changing the LED strip type
Edit `config.h`:
```cpp
// WS2812B (default)
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define LED_DATA_PIN 5
// APA102 / Dotstar (two-wire)
// #define LED_TYPE APA102
// #define COLOR_ORDER BGR
// #define LED_DATA_PIN 5
// #define LED_CLOCK_PIN 7
```
For APA102, also change the `FastLED.addLeds` call in `led_controller.cpp` to the two-pin form:
```cpp
FastLED.addLeds<LED_TYPE, LED_DATA_PIN, LED_CLOCK_PIN, COLOR_ORDER>(leds, NUM_LEDS);
```
---
## Multiple strips
To drive two strips with the same show, add a second array and register it in `led_controller.cpp`:
```cpp
static CRGB leds_a[NUM_LEDS];
static CRGB leds_b[NUM_LEDS];
void leds_begin() {
FastLED.addLeds<LED_TYPE, 5, COLOR_ORDER>(leds_a, NUM_LEDS);
FastLED.addLeds<LED_TYPE, 6, COLOR_ORDER>(leds_b, NUM_LEDS);
FastLED.setBrightness(MAX_BRIGHTNESS);
FastLED.clear(true);
}
void leds_apply_color(CRGB color) {
fill_solid(leds_a, NUM_LEDS, color);
fill_solid(leds_b, NUM_LEDS, color);
}
```
`FastLED.show()` pushes all registered strips at once — `leds_show()` needs no changes.
---
## Power budget reference
| Scenario | Current draw (approx.) |
|----------|----------------------|
| 60 LEDs, full white, brightness 255 | ~3.6A |
| 60 LEDs, full white, brightness 150 | ~2.1A |
| 60 LEDs, single color, brightness 150 | ~0.7A |
| 60 LEDs, all off | ~20mA (Arduino only) |
Always use a 5V supply rated for at least **1A more** than your calculated draw, and add a 1000µF capacitor on the supply lines near the strip connector.