Files
Amirine_Cosplay_Lights/DETAILS.md
T
2026-05-23 10:23:47 +02:00

5.5 KiB
Raw Blame History

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 library. A OneButton instance is created in cosplay_lights.ino with callbacks attached in setup():

  • Single tap → next show
  • Double tap → previous 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:

#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 6

// APA102 / Dotstar (two-wire)
// #define LED_TYPE     APA102
// #define COLOR_ORDER  BGR
// #define LED_DATA_PIN  6
// #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, 6, COLOR_ORDER>(leds_a, NUM_LEDS);
    FastLED.addLeds<LED_TYPE, 5, 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.