Rounds out the sampler with the remaining effects:
- bloom — a filled diamond grows from the centre to full, then collapses.
- breathe — a dithered shimmer that swells and fades (density ramp via a
fixed per-pixel threshold, since the matrix can't truly fade).
- checker — a checkerboard that inverts on each beat.
Full cycle is now rain -> knight-rider -> comet -> ripple -> bloom ->
breathe -> sparkle -> checker -> wipe. Compiled + flashed E2E (2057 bytes).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
207 lines
6.0 KiB
Arduino
207 lines
6.0 KiB
Arduino
// Matrix animation sampler for the Arduino Uno Q's 13x8 blue LED matrix.
|
|
// Cycles through several frame-based effects: rain -> knight-rider sweep ->
|
|
// comet -> ripple -> bloom -> breathe -> sparkle -> checker -> wipe, then
|
|
// repeats. All monochrome (pixels are on/off;
|
|
// the LEDs are physically blue — there is no colour or brightness control).
|
|
//
|
|
// Shared pattern: each effect fills the 8x13 `grid`, then show() packs it into
|
|
// the uint32_t[4] the driver wants and pushes it with loadFrame(). The only
|
|
// matrix calls that exist here are begin() and loadFrame().
|
|
//
|
|
// Compile: arduino-cli compile --fqbn arduino:zephyr:unoq --export-binaries
|
|
// Flash: arduino-flash <sketch>.ino.elf-zsk.bin (OpenOCD @ 0x80F0000)
|
|
|
|
#include "Arduino_LED_Matrix.h"
|
|
|
|
Arduino_LED_Matrix matrix;
|
|
|
|
static const uint8_t W = 13; // columns
|
|
static const uint8_t H = 8; // rows
|
|
|
|
bool grid[H][W];
|
|
uint32_t frame[4];
|
|
|
|
void clearGrid() {
|
|
for (uint8_t y = 0; y < H; y++)
|
|
for (uint8_t x = 0; x < W; x++) grid[y][x] = false;
|
|
}
|
|
|
|
// pack grid -> frame (row-major, MSB-first: pixel 0 = frame[0] bit 31) and show
|
|
void show(uint16_t ms) {
|
|
frame[0] = frame[1] = frame[2] = frame[3] = 0;
|
|
uint16_t bit = 0;
|
|
for (uint8_t y = 0; y < H; y++)
|
|
for (uint8_t x = 0; x < W; x++) {
|
|
if (grid[y][x]) frame[bit >> 5] |= (1UL << (31 - (bit & 31)));
|
|
bit++;
|
|
}
|
|
matrix.loadFrame(frame);
|
|
delay(ms);
|
|
}
|
|
|
|
// --- digital rain: per-column drops (head + short trail), staggered speeds ---
|
|
int8_t rHead[W];
|
|
uint8_t rLen[W], rPer[W], rPh[W];
|
|
void rainSeed(uint8_t x) {
|
|
rHead[x] = -(int8_t)random(0, H);
|
|
rLen[x] = random(2, 5);
|
|
rPer[x] = random(1, 4);
|
|
rPh[x] = 0;
|
|
}
|
|
void rain(uint16_t frames) {
|
|
for (uint8_t x = 0; x < W; x++) rainSeed(x);
|
|
for (uint16_t f = 0; f < frames; f++) {
|
|
clearGrid();
|
|
for (uint8_t x = 0; x < W; x++) {
|
|
for (uint8_t t = 0; t < rLen[x]; t++) {
|
|
int y = rHead[x] - t;
|
|
if (y >= 0 && y < H) grid[y][x] = true;
|
|
}
|
|
if (++rPh[x] >= rPer[x]) {
|
|
rPh[x] = 0;
|
|
rHead[x]++;
|
|
if (rHead[x] - (int8_t)rLen[x] >= (int8_t)H) rainSeed(x);
|
|
}
|
|
}
|
|
show(90);
|
|
}
|
|
}
|
|
|
|
// --- knight-rider: a full-height bar sweeps left<->right with a light trail ---
|
|
void knight(uint8_t sweeps) {
|
|
for (uint8_t s = 0; s < sweeps; s++) {
|
|
for (uint8_t dir = 0; dir < 2; dir++) {
|
|
for (int i = 0; i < W; i++) {
|
|
int x = dir ? (W - 1 - i) : i;
|
|
int xt = dir ? x + 1 : x - 1; // trailing column
|
|
clearGrid();
|
|
for (uint8_t y = 0; y < H; y++) {
|
|
grid[y][x] = true;
|
|
if (xt >= 0 && xt < W && (y % 2 == 0)) grid[y][xt] = true;
|
|
}
|
|
show(70);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- ripple: expanding square outline from the centre ---
|
|
void ripple(uint8_t reps) {
|
|
const int cx = 6, cy = 3;
|
|
for (uint8_t r0 = 0; r0 < reps; r0++) {
|
|
for (int r = 0; r < 8; r++) {
|
|
clearGrid();
|
|
for (int y = 0; y < H; y++)
|
|
for (int x = 0; x < W; x++)
|
|
if (max(abs(x - cx), abs(y - cy)) == r) grid[y][x] = true;
|
|
show(110);
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- sparkle: random pixels twinkle ---
|
|
void sparkle(uint16_t frames) {
|
|
for (uint16_t f = 0; f < frames; f++) {
|
|
clearGrid();
|
|
for (uint8_t k = 0; k < 10; k++) grid[random(0, H)][random(0, W)] = true;
|
|
show(70);
|
|
}
|
|
}
|
|
|
|
// --- wipe: fill column-by-column, then clear column-by-column ---
|
|
void wipe(uint8_t reps) {
|
|
for (uint8_t r = 0; r < reps; r++) {
|
|
for (uint8_t on = 0; on < 2; on++) {
|
|
for (int x = 0; x < W; x++) {
|
|
for (uint8_t y = 0; y < H; y++) grid[y][x] = (on == 0);
|
|
show(45);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- comet: a pixel bounces around the edges, trailing its last few cells ---
|
|
void comet(uint16_t frames) {
|
|
const uint8_t TRAIL = 5;
|
|
int hx[TRAIL], hy[TRAIL];
|
|
int x = random(0, W), y = random(0, H), dx = 1, dy = 1;
|
|
for (uint8_t i = 0; i < TRAIL; i++) { hx[i] = x; hy[i] = y; }
|
|
for (uint16_t f = 0; f < frames; f++) {
|
|
for (int i = TRAIL - 1; i > 0; i--) { hx[i] = hx[i - 1]; hy[i] = hy[i - 1]; }
|
|
hx[0] = x; hy[0] = y;
|
|
clearGrid();
|
|
for (uint8_t i = 0; i < TRAIL; i++) grid[hy[i]][hx[i]] = true;
|
|
show(70);
|
|
x += dx; y += dy; // advance, then reflect off walls
|
|
if (x < 0) { x = 1; dx = 1; }
|
|
else if (x > W - 1) { x = W - 2; dx = -1; }
|
|
if (y < 0) { y = 1; dy = 1; }
|
|
else if (y > H - 1) { y = H - 2; dy = -1; }
|
|
}
|
|
}
|
|
|
|
// --- bloom: a filled diamond grows from the centre to full, then collapses ---
|
|
void bloom(uint8_t reps) {
|
|
const int cx = 6, cy = 3;
|
|
for (uint8_t r0 = 0; r0 < reps; r0++) {
|
|
for (uint8_t dir = 0; dir < 2; dir++) {
|
|
for (int r = 0; r <= 8; r++) {
|
|
int rad = dir ? (8 - r) : r;
|
|
clearGrid();
|
|
for (int y = 0; y < H; y++)
|
|
for (int x = 0; x < W; x++)
|
|
if (max(abs(x - cx), abs(y - cy)) <= rad) grid[y][x] = true;
|
|
show(80);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- breathe: a dithered shimmer whose density swells and fades (no true fade,
|
|
// so we ramp how many pixels are lit via a fixed per-pixel threshold) ---
|
|
void breathe(uint8_t breaths) {
|
|
static uint8_t thr[H][W];
|
|
for (uint8_t y = 0; y < H; y++)
|
|
for (uint8_t x = 0; x < W; x++) thr[y][x] = random(0, 100);
|
|
for (uint8_t b = 0; b < breaths; b++) {
|
|
for (uint8_t dir = 0; dir < 2; dir++) {
|
|
for (int s = 0; s <= 100; s += 8) {
|
|
int level = dir ? (100 - s) : s;
|
|
clearGrid();
|
|
for (uint8_t y = 0; y < H; y++)
|
|
for (uint8_t x = 0; x < W; x++)
|
|
if (thr[y][x] < level) grid[y][x] = true;
|
|
show(45);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- checker: a checkerboard that inverts on each beat ---
|
|
void checker(uint8_t flips) {
|
|
for (uint8_t p = 0; p < flips; p++) {
|
|
clearGrid();
|
|
for (uint8_t y = 0; y < H; y++)
|
|
for (uint8_t x = 0; x < W; x++)
|
|
if (((x + y + p) & 1) == 0) grid[y][x] = true;
|
|
show(250);
|
|
}
|
|
}
|
|
|
|
void setup() {
|
|
matrix.begin();
|
|
randomSeed(micros());
|
|
}
|
|
|
|
void loop() {
|
|
rain(70);
|
|
knight(2);
|
|
comet(120);
|
|
ripple(2);
|
|
bloom(2);
|
|
breathe(2);
|
|
sparkle(40);
|
|
checker(6);
|
|
wipe(2);
|
|
}
|