NeoPixel fire and lava lamp
A flickering fire on a strip of NeoPixels, and a slow lava-lamp glow. The BOOT button switches between them.
Board
ESP32
3V3
GND
EN
23
VP
22
VN
TX
34
RX
35
21
32
GND
33
19
25
18
26
5
27
17
14
16
12
4
GND
0
13
2
D2
15
D3
D1
CMD
D0
5V
CLK
Wiring
| From | To board | Note |
|---|---|---|
| Strip 5V or VDC | 5V | Fine for a short strip at this brightness. Longer strips need their own 5 V supply |
| Strip GND | GND | Join the grounds if the strip has its own supply |
| Strip DIN, through 330 Ω | GPIO 16 | The resistor goes next to the strip |
| BOOT button | GPIO 0 | Already on the board. Press it to switch effects |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32 Dev Module
// Fire and lava lamp on a NeoPixel strip. BOOT switches the effect.
#include <Adafruit_NeoPixel.h>
const int PIN = 16;
const int COUNT = 30; // LEDs on your strip
const int BUTTON = 0; // the BOOT button
const int COOLING = 55; // fire: higher makes shorter flames
const int SPARKING = 120; // fire: higher makes a busier fire
Adafruit_NeoPixel strip(COUNT, PIN, NEO_GRB + NEO_KHZ800);
uint8_t heat[COUNT];
bool lava = false;
int lastButton = HIGH;
unsigned long frames = 0, reportAt = 0;
// Black, red, orange, yellow, white as heat rises.
uint32_t heatColor(uint8_t t) {
uint8_t scaled = (t * 191) / 255; // 0 to 191, three bands of 64
uint8_t ramp = (scaled & 63) << 2;
if (scaled > 127) return strip.Color(255, 255, ramp);
if (scaled > 63) return strip.Color(255, ramp, 0);
return strip.Color(ramp, 0, 0);
}
void fire() {
// The classic Fire2012 idea: each cell cools, heat drifts up, new sparks at the bottom.
for (int i = 0; i < COUNT; i++) {
int cool = random(0, ((COOLING * 10) / COUNT) + 2);
heat[i] = heat[i] > cool ? heat[i] - cool : 0;
}
for (int k = COUNT - 1; k >= 2; k--) heat[k] = (heat[k - 1] + 2 * heat[k - 2]) / 3;
if (random(255) < SPARKING) {
int y = random(min(7, COUNT));
heat[y] = min(255, heat[y] + (int)random(160, 255));
}
for (int j = 0; j < COUNT; j++) strip.setPixelColor(j, heatColor(heat[j]));
}
void lavaLamp() {
// Two slow waves of warm colour drifting past each other.
float t = millis() / 1000.0f;
for (int i = 0; i < COUNT; i++) {
float a = (sinf(i * 0.35f + t * 0.6f) + 1) / 2;
float b = (sinf(i * 0.17f - t * 0.4f + 2) + 1) / 2;
uint16_t hue = 60000 + (uint16_t)(a * 9000); // magenta through red to orange
uint8_t value = 60 + (uint8_t)(b * 195);
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(hue, 255, value)));
}
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON, INPUT_PULLUP);
strip.begin();
strip.setBrightness(60); // about a quarter: easy on USB power
strip.show();
randomSeed(esp_random());
Serial.printf("%d LEDs on GPIO %d. Press BOOT to switch effects.\n", COUNT, PIN);
Serial.println("mode: fire");
}
void loop() {
int button = digitalRead(BUTTON);
if (lastButton == HIGH && button == LOW) {
lava = !lava;
Serial.println(lava ? "mode: lava lamp" : "mode: fire");
delay(30);
}
lastButton = button;
if (lava) lavaLamp();
else fire();
strip.show();
frames++;
if (millis() - reportAt >= 5000) {
Serial.printf("fps=%lu\n", frames * 1000 / (millis() - reportAt));
frames = 0;
reportAt = millis();
}
delay(lava ? 30 : 16);
}S3
3V3
GND
3V3
TX
RST
RX
4
1
5
2
6
42
7
41
15
40
16
39
17
38
18
37
8
36
3
35
46
0
9
45
10
48
11
47
12
21
13
20
14
19
5V
GND
GND
GND
Wiring
| From | To board | Note |
|---|---|---|
| Strip 5V or VDC | 5V | Fine for a short strip at this brightness. Longer strips need their own 5 V supply |
| Strip GND | GND | Join the grounds if the strip has its own supply |
| Strip DIN, through 330 Ω | GPIO 5 | The resistor goes next to the strip |
| BOOT button | GPIO 0 | Already on the board. Press it to switch effects |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32S3 Dev Module
// Fire and lava lamp on a NeoPixel strip. BOOT switches the effect.
#include <Adafruit_NeoPixel.h>
const int PIN = 5;
const int COUNT = 30; // LEDs on your strip
const int BUTTON = 0; // the BOOT button
const int COOLING = 55; // fire: higher makes shorter flames
const int SPARKING = 120; // fire: higher makes a busier fire
Adafruit_NeoPixel strip(COUNT, PIN, NEO_GRB + NEO_KHZ800);
uint8_t heat[COUNT];
bool lava = false;
int lastButton = HIGH;
unsigned long frames = 0, reportAt = 0;
// Black, red, orange, yellow, white as heat rises.
uint32_t heatColor(uint8_t t) {
uint8_t scaled = (t * 191) / 255; // 0 to 191, three bands of 64
uint8_t ramp = (scaled & 63) << 2;
if (scaled > 127) return strip.Color(255, 255, ramp);
if (scaled > 63) return strip.Color(255, ramp, 0);
return strip.Color(ramp, 0, 0);
}
void fire() {
// The classic Fire2012 idea: each cell cools, heat drifts up, new sparks at the bottom.
for (int i = 0; i < COUNT; i++) {
int cool = random(0, ((COOLING * 10) / COUNT) + 2);
heat[i] = heat[i] > cool ? heat[i] - cool : 0;
}
for (int k = COUNT - 1; k >= 2; k--) heat[k] = (heat[k - 1] + 2 * heat[k - 2]) / 3;
if (random(255) < SPARKING) {
int y = random(min(7, COUNT));
heat[y] = min(255, heat[y] + (int)random(160, 255));
}
for (int j = 0; j < COUNT; j++) strip.setPixelColor(j, heatColor(heat[j]));
}
void lavaLamp() {
// Two slow waves of warm colour drifting past each other.
float t = millis() / 1000.0f;
for (int i = 0; i < COUNT; i++) {
float a = (sinf(i * 0.35f + t * 0.6f) + 1) / 2;
float b = (sinf(i * 0.17f - t * 0.4f + 2) + 1) / 2;
uint16_t hue = 60000 + (uint16_t)(a * 9000); // magenta through red to orange
uint8_t value = 60 + (uint8_t)(b * 195);
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(hue, 255, value)));
}
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON, INPUT_PULLUP);
strip.begin();
strip.setBrightness(60); // about a quarter: easy on USB power
strip.show();
randomSeed(esp_random());
Serial.printf("%d LEDs on GPIO %d. Press BOOT to switch effects.\n", COUNT, PIN);
Serial.println("mode: fire");
}
void loop() {
int button = digitalRead(BUTTON);
if (lastButton == HIGH && button == LOW) {
lava = !lava;
Serial.println(lava ? "mode: lava lamp" : "mode: fire");
delay(30);
}
lastButton = button;
if (lava) lavaLamp();
else fire();
strip.show();
frames++;
if (millis() - reportAt >= 5000) {
Serial.printf("fps=%lu\n", frames * 1000 / (millis() - reportAt));
frames = 0;
reportAt = millis();
}
delay(lava ? 30 : 16);
}C3
GND
GND
3V3
TX
3V3
RX
2
GND
3
9
GND
8
RST
GND
GND
7
0
6
1
5
10
4
GND
GND
5V
18
5V
19
GND
GND
Wiring
| From | To board | Note |
|---|---|---|
| Strip 5V or VDC | 5V | Fine for a short strip at this brightness. Longer strips need their own 5 V supply |
| Strip GND | GND | Join the grounds if the strip has its own supply |
| Strip DIN, through 330 Ω | GPIO 4 | The resistor goes next to the strip |
| BOOT button | GPIO 9 | Already on the board. Press it to switch effects |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32C3 Dev Module
// Fire and lava lamp on a NeoPixel strip. BOOT switches the effect.
#include <Adafruit_NeoPixel.h>
const int PIN = 4;
const int COUNT = 30; // LEDs on your strip
const int BUTTON = 9; // the BOOT button
const int COOLING = 55; // fire: higher makes shorter flames
const int SPARKING = 120; // fire: higher makes a busier fire
Adafruit_NeoPixel strip(COUNT, PIN, NEO_GRB + NEO_KHZ800);
uint8_t heat[COUNT];
bool lava = false;
int lastButton = HIGH;
unsigned long frames = 0, reportAt = 0;
// Black, red, orange, yellow, white as heat rises.
uint32_t heatColor(uint8_t t) {
uint8_t scaled = (t * 191) / 255; // 0 to 191, three bands of 64
uint8_t ramp = (scaled & 63) << 2;
if (scaled > 127) return strip.Color(255, 255, ramp);
if (scaled > 63) return strip.Color(255, ramp, 0);
return strip.Color(ramp, 0, 0);
}
void fire() {
// The classic Fire2012 idea: each cell cools, heat drifts up, new sparks at the bottom.
for (int i = 0; i < COUNT; i++) {
int cool = random(0, ((COOLING * 10) / COUNT) + 2);
heat[i] = heat[i] > cool ? heat[i] - cool : 0;
}
for (int k = COUNT - 1; k >= 2; k--) heat[k] = (heat[k - 1] + 2 * heat[k - 2]) / 3;
if (random(255) < SPARKING) {
int y = random(min(7, COUNT));
heat[y] = min(255, heat[y] + (int)random(160, 255));
}
for (int j = 0; j < COUNT; j++) strip.setPixelColor(j, heatColor(heat[j]));
}
void lavaLamp() {
// Two slow waves of warm colour drifting past each other.
float t = millis() / 1000.0f;
for (int i = 0; i < COUNT; i++) {
float a = (sinf(i * 0.35f + t * 0.6f) + 1) / 2;
float b = (sinf(i * 0.17f - t * 0.4f + 2) + 1) / 2;
uint16_t hue = 60000 + (uint16_t)(a * 9000); // magenta through red to orange
uint8_t value = 60 + (uint8_t)(b * 195);
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(hue, 255, value)));
}
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON, INPUT_PULLUP);
strip.begin();
strip.setBrightness(60); // about a quarter: easy on USB power
strip.show();
randomSeed(esp_random());
Serial.printf("%d LEDs on GPIO %d. Press BOOT to switch effects.\n", COUNT, PIN);
Serial.println("mode: fire");
}
void loop() {
int button = digitalRead(BUTTON);
if (lastButton == HIGH && button == LOW) {
lava = !lava;
Serial.println(lava ? "mode: lava lamp" : "mode: fire");
delay(30);
}
lastButton = button;
if (lava) lavaLamp();
else fire();
strip.show();
frames++;
if (millis() - reportAt >= 5000) {
Serial.printf("fps=%lu\n", frames * 1000 / (millis() - reportAt));
frames = 0;
reportAt = millis();
}
delay(lava ? 30 : 16);
}Parts
| Quantity | Part | Where to buy |
|---|---|---|
| 1× | ESP32 dev board | Buy at Amazon |
| 1× | WS2812B strip, 30 LEDs, or a NeoPixel Stick | Buy at Adafruit |
| 1× | 330 Ω resistor | |
| 1× | Optional 74AHCT125 level shifter | Buy at Adafruit |
| 3× | Jumper wires |
Amazon buy links may include an Associates tag, and this site may earn a commission from qualifying Amazon purchases at no extra cost to you. Adafruit links are direct.
Libraries
Adafruit NeoPixel
Load it
- In the Arduino IDE, install the esp32 board package and pick the board named above the sketch.
- Install the libraries listed above from the Library Manager.
- Paste the sketch, choose the board's port and upload. Hold BOOT if the upload stalls at Connecting.
- Close the IDE's serial monitor, then open the bench here to watch the output.
Field notes
- Each LED can draw about 60 mA at full white. Thirty LEDs at this brightness are fine on USB; at full brightness use a separate 5 V supply and a 1000 µF capacitor across it.
- WS2812B LEDs on 5 V expect data near 5 V. Most accept 3.3 V; if the first LED flickers or stays dark, put a 74AHCT125 level shifter in the data line.
- Hang the strip vertically for the fire: the flames rise from LED 0. A paper tube or a frosted jar over it makes it look far better.
- Tune COOLING and SPARKING to change the fire. The fps lines on the bench show how fast the strip is updating.
Not tested yet
- Not tested on hardware yet. Compiles for every board listed with Arduino-ESP32 core 3.3.12.
Built it? Send a build report with your board and what the serial port printed. Working reports become the "Tested on this board" entries above.