Haunted window on an Arduino Uno
A WS2812B strip across a window throws lightning in bursts of flashes while four LEDs on the sill flicker like candles, and a knob sets how wild the storm is. The whole build, an Arduino Uno and 5 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate Runs in your browser. Free, and no account needed.
Build it as the Halloween Prop Kit: the parts in numbered bags and a booklet that checks every step. Free.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Haunted window: a storm outside a haunted house. A WS2812B strip along the
// top of the window frame throws lightning, four more on the sill flicker
// like candles, and a knob sets how often the lightning strikes.
//
// sky strip 12 LEDs, DIN <- 330 ohms <- pin 6, VDD -> 5 V, VSS -> GND
// sill strip 4 LEDs, DIN <- 330 ohms <- pin 5, VDD -> 5 V, VSS -> GND
// knob 10 k potentiometer: ends to 5 V and GND, wiper to A0
//
// Real lightning is rarely a single flash. A stroke often comes in a quick
// burst of return strokes down the same channel, so here each strike is one
// to three flashes, each a few tens of milliseconds long, along one stretch
// of the strip, with a dim blue storm glow in between.
//
// The candles are the opposite: slow, warm and never quite still. Each one
// drifts toward a new random brightness every few frames, which reads as a
// flame far better than random blinking does.
//
// Nothing here uses delay(), so the candles keep flickering while the sky
// waits for its next strike.
//
// On the bench: give the strips their own 5 V supply sized for the LEDs you
// run, join its ground to the Uno's, and put a large capacitor (1000 uF is
// the usual advice) across the strip's 5 V and GND where the power comes in.
// Keep the 330 ohm resistor close to the first LED.
//
// Things to try. Turn the knob. Make the storm purple by changing STORM. Add
// a DFPlayer with a thunder track and play it a second or two after each
// strike, the way thunder trails a distant flash.
#include <Adafruit_NeoPixel.h>
const int SKY_PIN = 6;
const int SILL_PIN = 5;
const int KNOB_PIN = A0;
const int SKY_LEDS = 12;
const int SILL_LEDS = 4;
Adafruit_NeoPixel sky(SKY_LEDS, SKY_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel sill(SILL_LEDS, SILL_PIN, NEO_GRB + NEO_KHZ800);
const uint32_t STORM = Adafruit_NeoPixel::Color(0, 0, 10);
// The lightning.
unsigned long nextChange = 0;
int flashesLeft = 0;
bool flashOn = false;
int boltStart = 0;
int boltLength = 0;
// The candles: where each one is, and where it is drifting to.
int flame[SILL_LEDS];
int target[SILL_LEDS];
unsigned long nextFrame = 0;
void storm() {
sky.fill(STORM);
sky.show();
}
void flash() {
sky.fill(STORM);
int level = random(160, 256);
for (int i = boltStart; i < boltStart + boltLength && i < SKY_LEDS; i++) {
sky.setPixelColor(i, level, level, 255);
}
sky.show();
}
// How long until the next strike: a wilder storm (knob up) strikes sooner.
unsigned long quietTime() {
int knob = analogRead(KNOB_PIN);
unsigned long longest = map(knob, 0, 1023, 9000, 1200);
return random(longest / 3, longest);
}
void lightning(unsigned long now) {
if (now < nextChange) return;
if (flashOn) {
// End of a flash: back to the storm, then either the next flash of this
// strike or a long quiet.
flashOn = false;
storm();
nextChange = now + (flashesLeft > 0 ? random(50, 160) : quietTime());
return;
}
if (flashesLeft == 0) {
// A new strike.
flashesLeft = random(1, 4);
boltLength = random(4, SKY_LEDS + 1);
boltStart = random(0, SKY_LEDS - boltLength + 1);
Serial.print("Strike, ");
Serial.print(flashesLeft);
Serial.println(flashesLeft == 1 ? " flash" : " flashes");
}
flashesLeft--;
flashOn = true;
flash();
nextChange = now + random(30, 90);
}
void candles(unsigned long now) {
if (now < nextFrame) return;
nextFrame = now + 40;
for (int i = 0; i < SILL_LEDS; i++) {
if (random(0, 6) == 0) target[i] = random(90, 256);
flame[i] += (target[i] - flame[i]) / 3;
// A warm orange: lots of red, some green, almost no blue.
sill.setPixelColor(i, flame[i], flame[i] * 2 / 5, flame[i] / 40);
}
sill.show();
}
void setup() {
Serial.begin(9600);
randomSeed(analogRead(A1));
sky.begin();
sill.begin();
for (int i = 0; i < SILL_LEDS; i++) {
flame[i] = 180;
target[i] = 180;
}
storm();
nextChange = millis() + 600;
Serial.println("Storm rolling in");
}
void loop() {
unsigned long now = millis();
lightning(now);
candles(now);
}
Parts list
7 parts, plus the jumper wires. Every one is in the editor's parts bin.
- 1 × Arduino Uno R3
- 1 × Half breadboard
- 1 × Potentiometer
- 2 × Resistor, 330 Ω
- 2 × Addressable RGB LEDs
How it is wired
7 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Arduino Uno R3 pin 6; Resistor, 330 Ω (1) pin 1
- Arduino Uno R3 pin 5; Resistor, 330 Ω (2) pin 1
- Arduino Uno R3 pin 5V; Potentiometer pin 1; Addressable RGB LEDs (1) pin VDD; Addressable RGB LEDs (2) pin VDD
- Ground: Arduino Uno R3 pin GND; Potentiometer pin 3; Addressable RGB LEDs (1) pin VSS; Addressable RGB LEDs (2) pin VSS
- Arduino Uno R3 pin A0; Potentiometer pin 2
- Resistor, 330 Ω (1) pin 2; Addressable RGB LEDs (1) pin DIN
- Resistor, 330 Ω (2) pin 2; Addressable RGB LEDs (2) pin DIN
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.