/* we ❤️ wokwi.com
changes from the original (https://wokwi.com/arduino/projects/297779324291908105):
* runs on an ATmega328 rather than 2560. 641 LEDs won't easily work
with FastLED in only 2KB, but on each pin 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.
* switch 4 uses an experimental 2D mapper
TODO:
* variable length trails, so each letter's sequence can loop
seamlessly.
*/
#include <avr/pgmspace.h>
#include <FastLED.h>
#include "led_coords.h"
#include "led_sequences.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;
CRGBPalette16 palette2 = RainbowStripeColors_p;
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]);
}
uint16_t hue_frame_delta;
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 += 768;
base_hue += hue_frame_delta;
static uint8_t analog_value;
if (digitalRead(10))
analog_value = analogRead(A0) >> 2;
else
analog_value += 2;
uint8_t sw4 = digitalRead(9);
for (uint8_t pin = 0; pin < NUM_PINS; pin++) {
if (sw4)
map2d(pin, anim_offset);
else
perimeterMarch(pin, anim_offset, base_hue + 24 * 256 * pin);
applyShine(pin, anim_offset, analog_value);
FastLED[pin].showLedsInternal(255);
}
delay(20);
}
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;
}
}
}
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);
} 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);
uint16_t angle = pgm_read_word_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 = ((cos_angle >> 8) * fade_value) >> 8;
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
// align the shine with that on the strips
this_angle += 1 << 14;
// AVR can't divide quickly; hard-code the divisions
uint16_t angle_delta;
switch (length) {
case 16: angle_delta = 0xffff / 16; break;
case 56: angle_delta = 0xffff / 56; break;
case 64: angle_delta = 0xffff / 64; break;
default: angle_delta = 0xffff / length; break;
}
while (length--) {
cos_angle = cos16(this_angle);
if (cos_angle > 0) {
uint8_t this_fade = ((cos_angle >> 8) * fade_value) >> 8;
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;
}
}
}
// #pragma GCC push_options
// #pragma GCC optimize ("O0")
void map2d(const uint8_t pin, const uint16_t frame) {
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_word_near(&elements[element].angle);
int16_t x = pgm_read_word_near(&elements[element].x);
int16_t y = pgm_read_word_near(&elements[element].y);
element++;
uint16_t this_angle = (angle + 192) << 8;
int16_t pix_x = x * 32;
int16_t pix_y = y * 32;
switch (type) {
case 0: // strip
{ // without this {} block, `case 1` never runs. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=7508
// int16_t cosa = cos16(this_angle) / 2; // 1 == 16384
// int16_t sina = sin16(this_angle) / 2;
// int16_t delta_x = cosa - sina; // +/- 23,000 == +/- 1.41
// int16_t delta_y = sina + cosa;
// delta_x /= 64, delta_y /= 64;
int16_t delta_x = ((int32_t) (cos16(this_angle) - sin16(this_angle)) * 5) / 256;
int16_t delta_y = ((int32_t) (sin16(this_angle) + cos16(this_angle)) * 5) / 256;
// int16_t delta_x = (cos(this_angle * 2. * 3.14159 / 256.0) - sin(this_angle * 2. * 3.14159 / 256.0)) * 256.0;
// int16_t delta_y = (cos(this_angle * 2. * 3.14159 / 256.0) + sin(this_angle * 2. * 3.14159 / 256.0)) * 256.0;
pix_x -= delta_x * length / 2;
pix_y -= delta_y * length / 2;
int16_t this_hue = pix_x + frame * 256;
while (length--) {
leds[ledno++] = ColorFromPaletteExtended(palette2, this_hue, 255, LINEARBLEND);
pix_x += delta_x, pix_y += delta_y;
this_hue += delta_x;
}
}
break;
case 1: // ring
{
this_angle += 1 << 14;
while (length--) {
int16_t this_hue = pix_x + frame * 256;
// something, something, times radius, blah, blah
leds[ledno++] = ColorFromPaletteExtended(palette2, this_hue, 255, LINEARBLEND);
}
}
break;
default:
{
while (length--)
leds[ledno++] = CRGB(random8(), random8(), random8());
}
break;
}
}
}
#pragma GCC pop_options
pot1:GND
pot1:SIG
pot1:VCC
w1s1:DOUT
w1s1:VDD
w1s1:VSS
w1s1:DIN
w1s2:DOUT
w1s2:VDD
w1s2:VSS
w1s2:DIN
w1s3:DOUT
w1s3:VDD
w1s3:VSS
w1s3:DIN
w1s6:DOUT
w1s6:VDD
w1s6:VSS
w1s6:DIN
w1s4:DOUT
w1s4:VDD
w1s4:VSS
w1s4:DIN
w1r1:GND
w1r1:VCC
w1r1:DIN
w1r1:DOUT
w1s5:DOUT
w1s5:VDD
w1s5:VSS
w1s5:DIN
w1r2:GND
w1r2:VCC
w1r2:DIN
w1r2:DOUT
w1r3:GND
w1r3:VCC
w1r3:DIN
w1r3:DOUT
w1r4:GND
w1r4:VCC
w1r4:DIN
w1r4:DOUT
w1s7:DOUT
w1s7:VDD
w1s7:VSS
w1s7:DIN
w1s8:DOUT
w1s8:VDD
w1s8:VSS
w1s8:DIN
w1r5:GND
w1r5:VCC
w1r5:DIN
w1r5:DOUT
or2:GND
or2:VCC
or2:DIN
or2:DOUT
or1:GND
or1:VCC
or1:DIN
or1:DOUT
ks1:DOUT
ks1:VDD
ks1:VSS
ks1:DIN
ks2:DOUT
ks2:VDD
ks2:VSS
ks2:DIN
ks3:DOUT
ks3:VDD
ks3:VSS
ks3:DIN
ks4:DOUT
ks4:VDD
ks4:VSS
ks4:DIN
ks5:DOUT
ks5:VDD
ks5:VSS
ks5:DIN
ks6:DOUT
ks6:VDD
ks6:VSS
ks6:DIN
ks7:DOUT
ks7:VDD
ks7:VSS
ks7:DIN
ks8:DOUT
ks8:VDD
ks8:VSS
ks8:DIN
kr1:GND
kr1:VCC
kr1:DIN
kr1:DOUT
w2s1:DOUT
w2s1:VDD
w2s1:VSS
w2s1:DIN
w2s2:DOUT
w2s2:VDD
w2s2:VSS
w2s2:DIN
w2s3:DOUT
w2s3:VDD
w2s3:VSS
w2s3:DIN
w2s4:DOUT
w2s4:VDD
w2s4:VSS
w2s4:DIN
w2s5:DOUT
w2s5:VDD
w2s5:VSS
w2s5:DIN
w2s6:DOUT
w2s6:VDD
w2s6:VSS
w2s6:DIN
w2s7:DOUT
w2s7:VDD
w2s7:VSS
w2s7:DIN
w2s8:DOUT
w2s8:VDD
w2s8:VSS
w2s8:DIN
is1:DOUT
is1:VDD
is1:VSS
is1:DIN
is2:DOUT
is2:VDD
is2:VSS
is2:DIN
ir1:GND
ir1:VCC
ir1:DIN
ir1:DOUT
sw1:1
sw1:2
sw1:3
sw2:1
sw2:2
sw2:3
sw3:1
sw3:2
sw3:3
sw4:1
sw4:2
sw4:3
sw5:1
sw5:2
sw5:3