/*
* this code was generated on 2026-04-06
* it is the general result of a copilot response to the following COSTAR request
*
* context: rgb led strips with WS2812B leds, organised as 16x16 matrix, control with arduino
* and fastled oder neopixel library okay objective: I want to realise a monochromatic initial
* "screen" then create 1 to 5 circles forming their way from an arbitrary starting point by
* expanding their radius from the starting point until "leaving" the visiable area of the matrix
* stakeholder: engineering recipient task: creat example code for arduino ide, allow for config
* of the canvas colour and define the dregree of hue of the circle's colour relative to canvas,
* let me define with a variable also max number of circles
*
* and expanded by
* enhancement: yes, please enhance: allow for canvas gradient and anti-aliasing, remove blocking delays
*
*/
#include <FastLED.h>
// ---------------------------
// CONFIGURATION
// ---------------------------
#define LED_PIN 6
#define WIDTH 16
#define HEIGHT 16
#define NUM_LEDS (WIDTH * HEIGHT)
#define COLOR_ORDER GRB
#define CHIPSET WS2812B
#define BRIGHTNESS 120
// Canvas gradient (start → end hue)
//uint8_t canvasHueA = 50;
uint8_t canvasHueA = HUE_AQUA;
//uint8_t canvasHueB = 20;
uint8_t canvasHueB = HUE_ORANGE;
// Circle color relative to canvas
uint8_t circleHueOffset = 100;
// Circle behavior
uint8_t maxCircles = 3; // 1–5
uint16_t stepInterval = 50; // ms between radius increments
CRGB leds[NUM_LEDS];
bool roundActive = false;
uint32_t nextRoundTime = 0;
bool anyCircleSpawned = false;
// ---------------------------
// NON-BLOCKING TIMER
// ---------------------------
uint32_t lastStep = 0;
// ---------------------------
// XY MAPPING (serpentine)
// ---------------------------
uint16_t XY(uint8_t x, uint8_t y) {
if (y % 2 == 0) return y * WIDTH + x;
return y * WIDTH + (WIDTH - 1 - x);
}
// ---------------------------
// CIRCLE STRUCT
// ---------------------------
struct Circle {
int x, y;
float radius;
bool active;
uint32_t nextSpawnTime;
};
Circle circles[5];
// ---------------------------
// SPAWN NEW CIRCLES
// ---------------------------
/* void spawnCircles() {
for (int i = 0; i < maxCircles; i++) {
circles[i].x = random(0, WIDTH);
circles[i].y = random(0, HEIGHT);
circles[i].radius = 0;
circles[i].active = true;
}
} */
/*void scheduleCircle(int i) {
circles[i].active = false;
circles[i].radius = 0;
// Random delay before next spawn (200–2000 ms)
circles[i].nextSpawnTime = millis() + random(200, 2000);
} */
void startNewRound() {
roundActive = true;
anyCircleSpawned = false;
for (int i = 0; i < maxCircles; i++) {
circles[i].active = false;
circles[i].radius = 0;
circles[i].nextSpawnTime = millis() + random(300, 500);
}
}
// ---------------------------
// CANVAS GRADIENT
// ---------------------------
void drawGradientBackground() {
for (int y = 0; y < HEIGHT; y++) {
//float t = (float)y / (HEIGHT - 1);
//uint8_t hue = lerp8by8(canvasHueA, canvasHueB, t * 255);
// corrective code proposed by copilot
uint8_t amount = (y * 255) / (HEIGHT - 1);
uint8_t hue = lerp8by8(canvasHueA, canvasHueB, amount);
for (int x = 0; x < WIDTH; x++) {
//leds[XY(x, y)] = CHSV(hue, 255, 80);
leds[XY(x, y)] = CHSV(hue, 255, 200); // add more brightness to the background - realy needed? yes
}
}
}
// ---------------------------
// ANTI-ALIASED CIRCLE DRAW
// ---------------------------
void drawCircleAA(int cx, int cy, float radius, uint8_t hue) {
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
float dx = x - cx;
float dy = y - cy;
float dist = sqrt(dx*dx + dy*dy);
float diff = fabs(dist - radius);
// Anti-alias threshold
if (diff < 1.0f) {
uint8_t brightness = (1.0f - diff) * 255;
CRGB col = CHSV(hue, 255, brightness);
leds[XY(x, y)] += col; // additive blend
}
}
}
}
// ---------------------------
// DRAW FRAME
// ---------------------------
void drawFrame() {
drawGradientBackground();
/*for (int i = 0; i < maxCircles; i++) {
if (!circles[i].active) continue;
uint8_t circleHue = canvasHueA + circleHueOffset;
drawCircleAA(circles[i].x, circles[i].y, circles[i].radius, circleHue);
// Increase radius
circles[i].radius += 0.25f;
// Deactivate when fully outside
if (circles[i].radius > 40) {
circles[i].active = false;
}
}*/
/*for (int i = 0; i < maxCircles; i++) {
// If inactive, check whether it's time to spawn
if (!circles[i].active) {
if (millis() >= circles[i].nextSpawnTime) {
circles[i].x = random(0, WIDTH);
circles[i].y = random(0, HEIGHT);
circles[i].radius = 0;
circles[i].active = true;
}
continue;
}
// Active circle → draw it
uint8_t circleHue = canvasHueA + circleHueOffset;
drawCircleAA(circles[i].x, circles[i].y, circles[i].radius, circleHue);
circles[i].radius += 0.25f;
// When done, schedule next appearance
//if (circles[i].radius > 40) {
// scheduleCircle(i);
//}
}*/
bool allInactive = true;
for (int i = 0; i < maxCircles; i++) {
// If circle is inactive, check if it should spawn
if (!circles[i].active) {
if (roundActive && millis() >= circles[i].nextSpawnTime) {
circles[i].x = random(0, WIDTH);
circles[i].y = random(0, HEIGHT);
circles[i].radius = 0;
circles[i].active = true;
anyCircleSpawned = true; // <<< IMPORTANT
}
continue;
}
// Circle is active → draw it
allInactive = false;
uint8_t circleHue = canvasHueA + circleHueOffset;
drawCircleAA(circles[i].x, circles[i].y, circles[i].radius, circleHue);
circles[i].radius += 0.25f;
// When circle leaves the screen
if (circles[i].radius > 40) {
circles[i].active = false;
}
}
FastLED.show();
// If round is active and all circles are done → schedule next round
if (roundActive && allInactive && anyCircleSpawned) {
roundActive = false;
nextRoundTime = millis() + 6000;
}
// If round is inactive and it's time → start a new one
if (!roundActive && millis() >= nextRoundTime) {
startNewRound();
}
}
// ---------------------------
// SETUP
// ---------------------------
void setup() {
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
randomSeed(analogRead(0));
/*for (int i = 0; i < maxCircles; i++) {
scheduleCircle(i);
}*/
//spawnCircles();
startNewRound();
}
// ---------------------------
// MAIN LOOP (NON-BLOCKING)
// ---------------------------
void loop() {
uint32_t now = millis();
if (now - lastStep >= stepInterval) {
lastStep = now;
drawFrame();
}
// Respawn when all circles are done
bool allDone = true;
for (int i = 0; i < maxCircles; i++) {
if (circles[i].active) {
allDone = false;
break;
}
}
/*if (allDone) {
spawnCircles();
}*/
}