feat(uno-q): LED-matrix animation sketches (blue 13x8)
There was only a scroll-text template; add frame-based animations for the
Uno Q's built-in 13x8 monochrome-blue matrix. Both compiled (arduino:zephyr
0.51.0) and flashed E2E on hardware (OpenOCD @ 0x80F0000, per boards.txt).
- matrix_rain/ — digital rain: staggered per-column drops + trails.
- matrix_effects/ — sampler cycling rain / knight-rider / ripple / sparkle
/ wipe.
- README: the grid->uint32[4] packing pattern (row-major, MSB-first), how to
add an effect, and the compile/flash recipe (TMPDIR=/tmp trap + the correct
0x80F0000 address vs QClaw's stale 0x8100000).
Note: the matrix is physically blue + on/off only — no colour/brightness in
software.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
19593a637c
commit
2683c64c34
@@ -0,0 +1,50 @@
|
|||||||
|
# Uno Q LED-matrix sketches
|
||||||
|
|
||||||
|
Animations for the Arduino Uno Q's built-in **13×8 monochrome blue** LED matrix
|
||||||
|
(104 pixels on the STM32U585 MCU). The LEDs are physically blue and on/off only
|
||||||
|
— there is **no colour or brightness control** in software.
|
||||||
|
|
||||||
|
| Sketch | What it does |
|
||||||
|
|--------|--------------|
|
||||||
|
| `matrix_rain/` | Digital "rain" — per-column drops (head + short trail) at staggered speeds. |
|
||||||
|
| `matrix_effects/` | Sampler that cycles rain → knight-rider sweep → ripple → sparkle → wipe. |
|
||||||
|
|
||||||
|
## How they work
|
||||||
|
|
||||||
|
The matrix API is intentionally tiny: `matrix.begin()` and
|
||||||
|
`matrix.loadFrame(const uint32_t frame[4])`. There is **no** `drawFrame`,
|
||||||
|
`setPixel`, `clear`, etc. — those don't exist on this core and won't compile.
|
||||||
|
|
||||||
|
So each frame is built in an `8×13` boolean `grid`, then packed into the
|
||||||
|
`uint32_t[4]` (128 bits; 104 used) the driver wants — row-major, MSB-first, so
|
||||||
|
pixel 0 is `frame[0]` bit 31:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
uint16_t bit = 0;
|
||||||
|
for (uint8_t y = 0; y < 8; y++)
|
||||||
|
for (uint8_t x = 0; x < 13; x++) {
|
||||||
|
if (grid[y][x]) frame[bit >> 5] |= (1UL << (31 - (bit & 31)));
|
||||||
|
bit++;
|
||||||
|
}
|
||||||
|
matrix.loadFrame(frame);
|
||||||
|
```
|
||||||
|
|
||||||
|
To make a new effect, just fill `grid` differently each frame and call `show()`.
|
||||||
|
|
||||||
|
## Compile & flash (on the board, over adb)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
S=<adb-serial>
|
||||||
|
adb -s $S push matrix_effects /home/arduino/sketches/
|
||||||
|
# TMPDIR override dodges the adb shell's /data/local/tmp (breaks arduino-cli)
|
||||||
|
adb -s $S shell 'cd /home/arduino/sketches/matrix_effects && \
|
||||||
|
TMPDIR=/tmp arduino-cli compile --fqbn arduino:zephyr:unoq --export-binaries .'
|
||||||
|
adb -s $S shell 'arduino-flash /home/arduino/sketches/matrix_effects/build/arduino.zephyr.unoq/*.elf-zsk.bin'
|
||||||
|
```
|
||||||
|
|
||||||
|
`arduino-flash` runs OpenOCD (linuxgpiod SWD) and writes the sketch at
|
||||||
|
**`0x80F0000`** — the address in the board's `boards.txt`
|
||||||
|
(`unoq.upload.address`) for `arduino:zephyr` **0.51.0**. (Older QClaw docs cite
|
||||||
|
`0x8100000`; that's stale for this core — trust `boards.txt`.) The flash ends
|
||||||
|
with a reset, so the sketch runs immediately. Nothing may be visible for the
|
||||||
|
first ~20–30 s after a cold power-on while the boot logo owns the matrix.
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
// Matrix animation sampler for the Arduino Uno Q's 13x8 blue LED matrix.
|
||||||
|
// Cycles through several frame-based effects: rain -> knight-rider sweep ->
|
||||||
|
// ripple -> sparkle -> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
matrix.begin();
|
||||||
|
randomSeed(micros());
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
rain(70);
|
||||||
|
knight(2);
|
||||||
|
ripple(2);
|
||||||
|
sparkle(40);
|
||||||
|
wipe(2);
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Matrix-style "digital rain" for the Arduino Uno Q's 13x8 blue LED matrix.
|
||||||
|
// Frame-based animation: each loop builds an 8x13 pixel grid, packs it into the
|
||||||
|
// uint32_t[4] the Arduino_LED_Matrix driver expects, and pushes it with
|
||||||
|
// loadFrame(). The matrix is monochrome (pixels are on/off — no brightness), so
|
||||||
|
// each column's drop is drawn as a lit head plus a short trailing segment.
|
||||||
|
//
|
||||||
|
// Uno Q matrix API is deliberately tiny: matrix.begin() + matrix.loadFrame(frame).
|
||||||
|
// (No drawFrame/clear/setPixel — those do not exist here.)
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
int8_t head[W]; // row of each column's leading drop (starts above the top)
|
||||||
|
uint8_t len[W]; // length of the lit trail (head + tail)
|
||||||
|
uint8_t period[W]; // loops between downward steps (per-column speed)
|
||||||
|
uint8_t phase[W]; // step counter
|
||||||
|
uint32_t frame[4];
|
||||||
|
|
||||||
|
void reseed(uint8_t x) {
|
||||||
|
head[x] = -(int8_t)random(0, H); // stagger the start above the matrix
|
||||||
|
len[x] = random(2, 5); // 2..4 lit pixels
|
||||||
|
period[x] = random(1, 4); // 1 = fast, 3 = slow
|
||||||
|
phase[x] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
matrix.begin();
|
||||||
|
randomSeed(micros());
|
||||||
|
for (uint8_t x = 0; x < W; x++) reseed(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
bool grid[H][W];
|
||||||
|
for (uint8_t y = 0; y < H; y++)
|
||||||
|
for (uint8_t x = 0; x < W; x++) grid[y][x] = false;
|
||||||
|
|
||||||
|
for (uint8_t x = 0; x < W; x++) {
|
||||||
|
// draw the drop: head at head[x], tail extending upward
|
||||||
|
for (uint8_t t = 0; t < len[x]; t++) {
|
||||||
|
int y = head[x] - t;
|
||||||
|
if (y >= 0 && y < H) grid[y][x] = true;
|
||||||
|
}
|
||||||
|
// advance this column on its own cadence
|
||||||
|
if (++phase[x] >= period[x]) {
|
||||||
|
phase[x] = 0;
|
||||||
|
head[x]++;
|
||||||
|
if (head[x] - (int8_t)len[x] >= (int8_t)H) reseed(x); // fully off the bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pack grid -> frame: row-major, MSB-first (pixel 0 = frame[0] bit 31)
|
||||||
|
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(90);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user