/* we ❤️ wokwi.com
changes from the original (https://wokwi.com/arduino/projects/297779324291908105):
* runs on a Nano. 641 LEDs won't fit in 2KB, but the longest
string is only 174 LEDs. The same leds[174] array is re-used
for each letter.
* re-using leds[] means that nothing can depend on the previous
frame of animation, so fadeToBlackBy() had to go. Everything is
generated from scratch for each frame.
* uses a palette rather than HSV, so it's more easily customisable
* made it shiny. use switch 3 to move the light with the potentiometer.
TODO:
* variable length trails, so each letter's sequence can loop
seamlessly.
*/
#include <FastLED.h>
#include <avr/pgmspace.h>
#include "FastLED_additions.h"
// the longest string's length
#define NUM_LEDS 174
CRGB leds[NUM_LEDS];
// how many LEDs connected to each pin
const uint16_t pin_length[] PROGMEM = {174, 120, 104, 164, 52};
#define NUM_PINS (sizeof(pin_length) / sizeof(pin_length[0]))
CRGBPalette16 palette = RainbowColors_p;
uint16_t hue_frame_delta;
void setup() {
Serial.begin(115200);
pinMode(A0, INPUT);
for (uint8_t pin = 8; pin <= 12; pin++)
pinMode(pin, INPUT_PULLUP);
FastLED.setCorrection(UncorrectedColor);
FastLED.setTemperature(UncorrectedTemperature);
FastLED.addLeds<WS2812B, 2, GRB>(leds, pin_length[0]);
FastLED.addLeds<WS2812B, 3, GRB>(leds, pin_length[1]);
FastLED.addLeds<WS2812B, 4, GRB>(leds, pin_length[2]);
FastLED.addLeds<WS2812B, 5, GRB>(leds, pin_length[3]);
FastLED.addLeds<WS2812B, 6, GRB>(leds, pin_length[4]);
}
void loop() {
static uint16_t anim_offset, base_hue;
anim_offset--;
hue_frame_delta = 64;
if (digitalRead(12))
hue_frame_delta += 256 * 128;
if (digitalRead(11))
hue_frame_delta += 512;
base_hue += hue_frame_delta;
static uint8_t analog_value;
if (digitalRead(10))
analog_value = analogRead(A0) >> 2;
else
analog_value += 2;
for (uint8_t pin = 0; pin < NUM_PINS; pin++) {
perimeterMarch(pin, anim_offset, base_hue + 24 * 256 * pin);
applyShine(pin, anim_offset, analog_value);
FastLED[pin].showLeds();
}
delay(1);
}
// an array of which describes a route around the perimeter of each letter
struct Span {
uint16_t start;
int8_t length;
};
const uint8_t spans_per_pin[] PROGMEM = {16, 2, 13, 2, 1};
const Span perimeter_spans[] PROGMEM = {
// W
{0, 12}, // up the LHS of left stroke of W
{12, 16}, // around ring1
{28, 12}, // back down RHS of left stroke
{40, 2}, // 2 dots of ring2
{56, 10}, // up the LHS shallow centre stroke
{72, 6}, // up from join of LHS of the steep centre stroke
{78, 16}, // around ring3
{94, 12}, // down RHS of steep centre
{106, 2}, // 2 dots around ring4
{132, 13}, // up the LHS of right stroke
{145, 16}, // around ring5
{161, 13}, // down the RHS of right stroke
{108, 14}, // remainder of ring4
{66, 6}, // up to join of LHS of the steep centre stroke
{122, 10}, // down RHS of shallow centre stroke
{42, 14}, // around the remainder of ring2
// o
{0, 64}, // clockwise around outer ring
{64, -56}, // anti-clockwise around inner ring
// k
{0, 3}, // 3 dots around ring
{16, -10}, // up LHS of top vert
{26, -10}, // down RHS of top vert
{3, 3}, // 3 more dots of ring
{36, -12}, // up LHS of top right
{48, -12}, // down RHS of top right
{6, 4}, // 4 more dots of ring
{60, 12}, // down RHS of bottom right
{72, 12}, // up LHS of bottom right
{10, 4}, // 4 more dots of ring
{84, -10}, // down RHS of bottom vert
{94, -10}, // up LHS of bottom vert
{14, 2}, // last 2 dots of ring
// w (max length is 127, so I split it into two)
{0, 102}, {102, 62},
// i
{0, 52},
};
void perimeterMarch(uint8_t pin, uint16_t anim_offset, uint16_t hue) {
uint8_t span = 0;
uint8_t ipin = 0;
while (ipin < pin)
span += pgm_read_byte_near(&spans_per_pin[ipin++]);
uint8_t spans_remaining = pgm_read_byte_near(&spans_per_pin[pin]);
while (spans_remaining--) {
int8_t length = pgm_read_byte_near(&perimeter_spans[span].length);
uint16_t ledno = pgm_read_word_near(&perimeter_spans[span++].start);
int8_t step = 1;
if (length < 0) {
length = -length;
ledno += length - 1;
step = -1;
}
while (length--) {
uint8_t subpos = anim_offset++ % 32;
if (subpos == 0)
hue += 0;
// to mimic the previous fadeToBlackBy() behaviour the hue
// needs to match what was drawn on previous frames
uint16_t aged_hue = hue - hue_frame_delta * (31 - subpos);
uint8_t aged_brightness = 7 + subpos * 8;
// leds[ledno] = CHSV(aged_hue >> 8, 255, aged_brightness);
leds[ledno] = ColorFromPaletteExtended(palette, aged_hue, aged_brightness, LINEARBLEND);
ledno += step;
}
}
}
// a list of LED strips/rings and their coordinates and angle
struct Element {
uint16_t start;
uint8_t length;
uint8_t type; // 0 -> strip, 1 -> ring
uint8_t angle;
int16_t x;
int16_t y;
};
constexpr uint8_t deg2byte(uint32_t degrees) {
return degrees * 256 / 360;
}
const uint8_t elements_per_pin[] PROGMEM = {13, 2, 9, 8, 3};
const Element elements[] PROGMEM = {
{0, 12, 0, deg2byte(160), -1236, 69}, // w1s1
{12, 16, 1, deg2byte(180), -1348, -73}, // w1r1
{28, 12, 0, deg2byte(340), -1214, 63}, // w1s2
{40, 16, 1, deg2byte(0), -1227, 321}, // w1r2
{56, 10, 0, deg2byte(225), -1053, 136}, // w1s3
{66, 12, 0, deg2byte(159), -968, 42}, // w1s4
{78, 16, 1, deg2byte(175), -1072, -97}, // w1r3
{94, 12, 0, deg2byte(339), -935, 38}, // w1s5
{106, 16, 1, deg2byte(355), -940, 296}, // w1r4
{122, 10, 0, deg2byte(45), -1039, 154}, // w1s6
{132, 13, 0, deg2byte(200), -810, 9}, // w1s7
{145, 16, 1, deg2byte(210), -782, -126}, // w1r5
{161, 13, 0, deg2byte(20), -790, 18}, // w1s8
{0, 64, 1, deg2byte(0), -633, -40}, // or1
{64, 56, 1, deg2byte(180), -610, -15}, // or2
{0, 16, 1, deg2byte(270), -88, 111}, // kr1
{16, 10, 0, deg2byte(180), -80, -95}, // ks1
{26, 10, 0, deg2byte(0), -58, -97}, // ks2
{36, 12, 0, deg2byte(233), 93, -111}, // ks3
{48, 12, 0, deg2byte(43), 110, -96}, // ks4
{60, 12, 0, deg2byte(315), 122, 178}, // ks5
{72, 12, 0, deg2byte(135), 109, 198}, // ks6
{84, 10, 0, deg2byte(0), -59, 250}, // ks7
{94, 10, 0, deg2byte(180), -82, 245}, // ks8
{0, 22, 0, deg2byte(165), 367, -61}, // w2s1
{22, 20, 0, deg2byte(345), 380, -76}, // w2s2
{42, 20, 0, deg2byte(200), 522, -82}, // w2s3
{62, 20, 0, deg2byte(348), 660, -76}, // w2s4
{82, 20, 0, deg2byte(200), 797, -65}, // w2s5
{102, 22, 0, deg2byte(20), 807, -48}, // w2s6
{124, 20, 0, deg2byte(168), 653, -8}, // w2s7
{144, 20, 0, deg2byte(20), 523, -20}, // w2s8
{0, 18, 0, deg2byte(180), 1034, 50}, // is1
{18, 16, 1, deg2byte(190), 987, -116}, // ir1
{34, 18, 0, deg2byte(0), 1058, 50}, // is2
};
void applyShine(uint8_t pin, uint16_t frame, uint8_t analog_value) {
static uint32_t change_ms;
static uint8_t light_angle;
static uint8_t fade_value;
if (analog_value != light_angle) {
light_angle = analog_value;
// if (light_angle < analog_value)
// light_angle++;
// else
// light_angle--;
change_ms = millis();
}
if (millis() - change_ms < 1500) {
fade_value = qadd8(fade_value, 3);
if (fade_value > 180)
fade_value = 180;
} else
fade_value = qsub8(fade_value, 3);
if (fade_value == 0)
return;
uint8_t element = 0;
uint8_t ipin = 0;
while (ipin < pin)
element += pgm_read_byte_near(&elements_per_pin[ipin++]);
uint8_t elements_remaining = pgm_read_byte_near(&elements_per_pin[pin]);
while (elements_remaining--) {
uint16_t ledno = pgm_read_word_near(&elements[element].start);
uint8_t length = pgm_read_byte_near(&elements[element].length);
uint8_t type = pgm_read_byte_near(&elements[element].type);
uint8_t angle = pgm_read_byte_near(&elements[element].angle);
element++;
uint16_t this_angle = (light_angle - angle) << 8;
int16_t cos_angle;
switch (type) {
case 0: // strip
cos_angle = cos16(this_angle);
if (cos_angle > 0) {
uint8_t this_fade = ((int32_t)cos_angle * fade_value) >> 15;
while (length--) {
leds[ledno] = CRGB(qadd8(leds[ledno].r, this_fade),
qadd8(leds[ledno].g, this_fade),
qadd8(leds[ledno].b, this_fade));
ledno++;
}
}
break;
case 1: // ring
uint16_t angle_delta = 0xffff / length;
this_angle += 1 << 14;
while (length--) {
cos_angle = cos16(this_angle);
if (cos_angle > 0) {
uint8_t this_fade = ((int32_t)cos_angle * fade_value) >> 15;
leds[ledno] = CRGB(qadd8(leds[ledno].r, this_fade),
qadd8(leds[ledno].g, this_fade),
qadd8(leds[ledno].b, this_fade));
}
this_angle -= angle_delta;
ledno++;
}
break;
default:
break;
}
}
}