#include <Adafruit_NeoPixel.h>
#define PIN 0 // WS2812 data pin on ATtiny85
#define MATRIX_WIDTH 8
#define MATRIX_HEIGHT 4
#define NUMPIXELS (MATRIX_WIDTH * MATRIX_HEIGHT)
// Zigzag (serpentine) layout object
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
// Wave animation parameters
float wavePos = 0.0; // Tracks the wave's diagonal position
float waveSpeed = 0.05; // Wave movement speed (lower = slower, smoother)
// We’ll define the diagonal “start” and “end” for wavePos
// Bottom-right corner => x=7, y=3 => x+y=10
// Top-left corner => x=0, y=0 => x+y=0
// We'll let wavePos go from +10 (fully at bottom-right) down to -4 (fully off top-left), then repeat
float waveStart = 10.0; // wavePos when the wave center is at bottom-right
float waveEnd = -4.0; // wavePos when the wave has fully left the top-left
// Convert (x, y) to NeoPixel index in a zigzag layout
int getPixelIndex(int x, int y) {
// Even rows: left to right
// Odd rows: right to left
if (y % 2 == 0) {
return y * MATRIX_WIDTH + x;
} else {
return y * MATRIX_WIDTH + (MATRIX_WIDTH - 1 - x);
}
}
void setup() {
strip.begin();
strip.show();
// Start the wave fully at bottom-right corner
wavePos = waveStart;
}
// Helper: map distance -> brightness with a narrow wave shape
uint8_t waveBrightness(float distance) {
// Increase multiplier to make the wave narrower
// e.g. 180 means at distance=1, brightness drops ~180 => 75 left
uint16_t fadeFactor = 180;
// Distance-based brightness
// distance=0 => brightness=255
// distance=1 => brightness ~ 75
// distance=1.4 => near 0
int b = 255 - (int)(abs(distance) * fadeFactor);
if (b < 0) b = 0;
if (b > 255) b = 255;
return (uint8_t)b;
}
void loop() {
// Clear first so we only see the wave
strip.clear();
// Update every pixel
for (int y = 0; y < MATRIX_HEIGHT; y++) {
for (int x = 0; x < MATRIX_WIDTH; x++) {
// distance from the wave center
float distance = (x + y) - wavePos;
// Convert distance -> brightness
uint8_t b = waveBrightness(distance);
if (b > 0) {
// Create a hue in the strict blue range [160..240]
// Just a small variation so each diagonal pixel isn't the exact same color
// Adjust as you like or remove variation entirely
uint8_t baseHue = 160; // Start of blue range
uint8_t hueRange = 80; // 160..239
uint8_t hue = baseHue + ((uint8_t)((x + y) * 5) % hueRange);
// Convert hue to a 16-bit value for ColorHSV
uint16_t hue16 = (uint16_t)hue * 256;
// Get the final color (HSV -> RGB) with full saturation
uint32_t color = strip.ColorHSV(hue16, 255, b);
strip.setPixelColor(getPixelIndex(x, y), color);
}
}
}
strip.show();
// Move the wave from bottom-right to top-left
wavePos -= waveSpeed;
// Once the wave has fully left the top-left corner, reset
if (wavePos < waveEnd) {
wavePos = waveStart;
}
// Lower delay => smoother but uses more CPU
delay(25);
}