- 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>
5.6 KiB
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):
- Check the button for events; switch show if needed (
load_show()). - Read the current step from
SHOW[].stepsin PROGMEM. - Compute progress
t(0–255) aselapsed * 255 / duration_ms, clamped. - Blend
s_from_colortoward the target color using FastLED'sblend(). - Push the blended color to the LEDs.
- If
t == 255, advance to the next step. ForSHOW_LOOPshows this wraps; forSHOW_SINGLEshows 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 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:
- Reads every
.txtinconverter/shows/. - Converts
blue_breath.txtfirst (always index 0), then all others alphabetically. - Writes one
show_<name>.hper file intoarduino/cosplay_lights/. - Regenerates
shows.hwith updated#includelines andSHOWS[]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:
#define PATTERN_SOLID 0
#define PATTERN_CHASE 1
#define ACTIVE_PATTERN PATTERN_CHASE
Step 2 — Add the pattern logic in led_controller.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:
// 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:
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:
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.