#include <Adafruit_NeoPixel.h>
// --- Configuration ---
#define LED_PIN 2 // The Arduino pin connected to the NeoPixel strip's Data In
#define LED_COUNT 60 // Number of LEDs on your strip
#define BRIGHTNESS 200 // Set brightness (0-255). Be mindful of power consumption.
// Create an instance of the Adafruit_NeoPixel class
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
void setup() {
// Initialize the NeoPixel strip
strip.begin();
strip.setBrightness(BRIGHTNESS);
strip.show(); // Initialize all pixels to 'off'
}
void loop() {
// Continuously run the bouncing balls effect
bouncingBalls();
}
/**
* @brief Bouncing Balls Effect
* Simulates two "balls" of light that are randomly "kicked" upwards from the
* bottom of the strip. They then fall back down with simulated gravity.
* A fading trail is left behind each ball.
*/
void bouncingBalls() {
// Static variables are crucial here. They preserve their values between
// each call to the loop(), allowing the balls to remember their position and velocity.
static float pos1 = 0, vel1 = 0; // Position and velocity for ball 1
static float pos2 = 0, vel2 = 0; // Position and velocity for ball 2
// This constant determines how strongly the balls are pulled "down" (towards pixel 0).
// A larger value means they fall faster.
const float gravity = -0.4;
// --- Step 1: Fade all existing pixels ---
// This creates the "trails" behind the moving balls.
// The 0.85 multiplier means each pixel loses 15% of its brightness each frame.
for(int i = 0; i < strip.numPixels(); i++) {
uint32_t color = strip.getPixelColor(i);
uint8_t r = (uint8_t)( (color >> 16) * 0.85 );
uint8_t g = (uint8_t)( ((color >> 8) & 0xFF) * 0.85 );
uint8_t b = (uint8_t)( (color & 0xFF) * 0.85 );
strip.setPixelColor(i, strip.Color(r, g, b));
}
// --- Step 2: Update physics for Ball 1 ---
vel1 = vel1 + gravity; // Apply gravity to velocity
pos1 = pos1 + vel1; // Apply velocity to position
// Check if the ball has hit the "floor" (or gone below it)
if (pos1 < 0) {
pos1 = 0; // Reset position to the floor
// Give the ball a random chance to be "kicked" back up
if (random(100) < 40) { // 40% chance of a new kick
// Give it a new random upward velocity
vel1 = random(60, 95) / 10.0; // Random value between 6.0 and 9.5
}
}
// --- Step 3: Update physics for Ball 2 ---
// This ball is updated independently with different random chances
vel2 = vel2 + gravity;
pos2 = pos2 + vel2;
if (pos2 < 0) {
pos2 = 0;
// This ball has a different chance and velocity range for variety
if (random(100) < 25) { // 25% chance of a new kick
vel2 = random(50, 80) / 10.0; // Random value between 5.0 and 8.0
}
}
// --- Step 4: Draw the balls at their new positions ---
// Use constrain to make sure the position is a valid pixel index
int intPos1 = constrain((int)pos1, 0, LED_COUNT - 1);
int intPos2 = constrain((int)pos2, 0, LED_COUNT - 1);
// Draw the two balls with distinct, bright colors
strip.setPixelColor(intPos1, strip.Color(255, 0, 100)); // Hot Pink
strip.setPixelColor(intPos2, strip.Color(0, 100, 255)); // Bright Blue
// --- Step 5: Update the physical LED strip ---
strip.show();
// A short delay to control the animation speed.
// Smaller delay = faster animation.
delay(20);
}