/* 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