a96f378c9c
- 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>
159 lines
5.6 KiB
Markdown
159 lines
5.6 KiB
Markdown
# 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` (0–255) 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.
|