A customizable LED light effects controller for Arduino Nano with NeoPixel LED strips, featuring multiple effects with real-time control via potentiometers and gamma correction for perceptually smooth brightness transitions.
This project provides 8 different lighting effects with intuitive control:
- Off - All LEDs turned off
- White Light - Adjustable color temperature (warm to cool white)
- Solid Hue - Solid color with full hue control
- Pulse Hue - Pulsing/breathing effect in selected color
- Chase Hue - Single LED chasing along the strip
- Rainbow Fade - Rainbow pattern with fade in/out
- Fire Effect - Realistic fire simulation with 6 color palettes (classic, hot, toxic, purple, ice, inferno)
- White Flicker - Random white flicker effect (3 LEDs at a time)
Check this video to see the completed project: https://youtu.be/B0sxnHQu-bU
- Arduino Nano (or compatible)
- NeoPixel LED Strip (WS2812/WS2812B) - 12 LEDs (configurable)
- 3x Potentiometers (5kΩ or 10kΩ linear)
- 1x Push Button (tactile switch)
- 1x 470Ω Resistor (for LED data line protection)
- 1x Electrolytic Capacitor (6.3V or higher, for power stability)
- 220µF - 470µF: Suitable for ≤12 LEDs
- 1000µF: Recommended for larger setups or longer wires
- Breadboard and jumper wires
- Power supply (5V, adequate for your LED strip)
Arduino Pin 6 → 470Ω Resistor → LED Strip Data Pin
LED Strip 5V → 5V Power Supply
LED Strip GND → Common Ground
Capacitor (220µF - 1000µF):
Positive (+) → LED Strip 5V (near strip input)
Negative (-) → LED Strip GND (near strip input)
Notes:
- The 470Ω resistor protects the LED strip's data input and improves signal integrity by reducing reflections.
- The capacitor (220µF-1000µF, 6.3V or higher) should be placed as close as possible to the LED strip's power input. This prevents voltage drops and flicker caused by sudden current surges when LEDs turn on. 220µF-470µF is sufficient for small setups (≤12 LEDs), while 1000µF is recommended for larger installations or longer wire runs.
- Important: Electrolytic capacitors are polarized - connect positive (+) to 5V and negative (-) to GND. Reverse polarity can damage the capacitor.
Brightness Potentiometer (POT 1):
Left Pin → GND
Center Pin → Arduino A0
Right Pin → 5V
Hue/Warmth Potentiometer (POT 2):
Left Pin → GND
Center Pin → Arduino A1
Right Pin → 5V
Speed Potentiometer (POT 3):
Left Pin → GND
Center Pin → Arduino A2
Right Pin → 5V
Note: This wiring gives intuitive left-to-right control (0→1023). You can reverse GND/5V if you prefer the opposite direction.
Button Pin 1 → Arduino Pin 2
Button Pin 2 → GND
Note: The code uses the internal pull-up resistor, so no external resistor is needed.
Arduino 5V → 5V Power Supply
Arduino GND → Common Ground
Important: For LED strips with more than a few LEDs, use an external 5V power supply (not USB power) to avoid overloading the Arduino.
- Adafruit NeoPixel - Install via Arduino Library Manager or PlatformIO
-
Clone or download this repository
git clone <repository-url>
-
Open in PlatformIO or Arduino IDE
- For PlatformIO: Open the project folder
- For Arduino IDE: Open
src/main.cpp(rename to.inoif needed)
-
Install dependencies
- PlatformIO will auto-install from
platformio.ini - Arduino IDE: Install "Adafruit NeoPixel" library
- PlatformIO will auto-install from
-
Configure LED count (if different from 12)
- Edit
LED_COUNTinmain.cpp(line 6)
- Edit
-
Upload to Arduino Nano
- Single Press: Cycle through effects (0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 0...)
- Effect changes are indicated in the Serial Monitor
| Potentiometer | Pin | Function |
|---|---|---|
| Brightness | A0 | Controls overall brightness (0-255) for all effects |
| Hue/Warmth | A1 | Effect-dependent: • White Light: Color temperature (warm to cool) • Other effects: Color hue selection |
| Speed | A2 | Controls animation speed (10-1000ms delay, left = slowest) |
- Turns off all LEDs
- No potentiometer controls active
- Brightness (A0): Adjusts light intensity
- Warmth (A1): Color temperature control
- Low (0): Very warm candlelight (orange glow)
- Mid (512): Warm white
- High (1023): Cool daylight (bluish white)
- Speed (A2): Not used
- 8-point color temperature gradient (5 warm, 3 cool)
- Brightness (A0): Adjusts light intensity
- Hue (A1): Full color spectrum selection
- Speed (A2): Not used
- Displays a solid color across all LEDs
- Brightness (A0): Sets maximum brightness for pulse
- Hue (A1): Color selection
- Speed (A2): Pulse speed (breathing rate)
- Smoothly fades in and out
- Brightness (A0): LED brightness
- Hue (A1): Color selection
- Speed (A2): Chase speed
- Single LED moves along the strip
- Brightness (A0): Sets maximum brightness for fade
- Hue (A1): Not used (automatic rainbow)
- Speed (A2): Fade and rotation speed
- Rainbow pattern that rotates and fades
- Brightness (A0): Overall fire intensity
- Hue (A1): Selects fire color palette (6 options):
- Classic Fire (pot low): Red → Orange → Yellow (traditional campfire)
- Hot Fire: Orange → Yellow → White (intense heat)
- Toxic Fire: Green → Cyan → Blue (chemical/magical flame)
- Purple Fire: Purple → Magenta → Pink (mystical fire)
- Ice Fire: Blue → Cyan → White (cold flame)
- Inferno (pot high): Dark Red → Red → Orange (deep volcanic fire)
- Speed (A2): Flicker rate (lower = slower flicker, higher = rapid flicker)
- Simulates realistic fire with:
- Position-based color gradient (bottom LEDs = inner fire color, top LEDs = outer flame color)
- Random brightness flickering (60-100% variation)
- Occasional deep dimming (30% chance) for dynamic effect
- Brightness (A0): Flicker intensity
- Hue (A1): Not used (always white)
- Speed (A2): Flicker rate
- Random white flicker (3 LEDs at a time)
Connect to the Serial Monitor (9600 baud) to see:
- Current effect name when switching
- Real-time potentiometer readings (every 1 second)
- Calculated values (brightness, hue, speed, RGB colors)
- Hardware diagnostics on startup
Example output:
Button pressed! Switching to effect 1: White Light
[White Light] Brightness pot: 512 -> 128 | Warmth pot: 300 -> RGB(255,198,147)
#define LED_PIN 6 // NeoPixel data pin
#define LED_COUNT 12 // Number of LEDs in strip
#define BUTTON_PIN 2 // Button pin
#define POT_PIN_BRIGHTNESS A0 // Brightness pot
#define POT_PIN_HUE A1 // Hue/warmth pot
#define POT_PIN_SPEED A2 // Speed pot
// Potentiometer calibration (adjust for your hardware)
#define POT_MIN 15 // Actual minimum value your pots reach
#define POT_MAX 1000 // Actual maximum value your pots reachDue to hardware tolerances, potentiometers rarely reach the full theoretical
range of 0-1023. The POT_MIN and POT_MAX defines compensate for this margin
of error, ensuring you can access the full range of all effects (e.g., all fire
palettes, all white temperatures, full brightness range).
To calibrate for your specific hardware:
- Upload the code and open Serial Monitor (9600 baud)
- Select any effect that shows potentiometer values (e.g., Effect 2: Solid Hue)
- Turn each potentiometer fully counter-clockwise and note the lowest "raw" value shown
- Turn each potentiometer fully clockwise and note the highest "raw" value shown
- Update the defines in main.cpp (lines 18-19):
#define POT_MIN 15 // Use the lowest value you observed #define POT_MAX 1000 // Use the highest value you observed
Example Serial Monitor output:
[Solid Hue] Brightness pot: 15 -> 0 | Hue pot: 15 -> 0 | Speed pot: N/A
[Solid Hue] Brightness pot: 1000 -> 255 | Hue pot: 1000 -> 65535 | Speed pot: N/A
In this example, the pots range from 15 to 1000 (not the theoretical 0 to 1023),
so POT_MIN = 15 and POT_MAX = 1000.
Effects improved by calibration:
- Fire Effect: All 6 palettes become accessible (especially Inferno at high end)
- White Light: Full temperature range from very warm to very cool
- All effects: True minimum (0) and maximum (255/65535) values are reachable
- Adjust in
readSpeedFromPot()function (line 96) - Default:
map(filtered, POT_MIN, POT_MAX, 1000, 10)(pot left = 1000ms very slow, pot right = 10ms fast) - For overall slower effects: Use higher values (e.g.,
map(filtered, POT_MIN, POT_MAX, 2000, 50)) - For overall faster effects: Use lower values (e.g.,
map(filtered, POT_MIN, POT_MAX, 500, 5))
- Potentiometer readings use exponential moving average
- Adjust smoothing in helper functions (lines 47, 70, 93)
- Current:
(filtered * 7 + raw) / 8 - More responsive:
(filtered * 3 + raw) / 4 - More stable:
(filtered * 15 + raw) / 16
All effects use gamma correction (gamma=2.6) for perceptually linear brightness transitions. This provides:
- Smoother fades: Pulse and Rainbow effects appear to fade evenly instead of changing quickly at low brightness and slowly at high brightness
- Better color accuracy: Color ratios remain consistent across all brightness levels
- Realistic fire effect: Low brightness flickers show better detail with realistic "ember glow"
- Accurate white temperature: The 8-point color temperature gradient maintains accurate warmth appearance at all brightness levels
Gamma correction is applied using strip.gamma32(color) in all effect
functions. This compensates for the non-linear relationship between LED power
levels and human brightness perception.
To disable gamma correction, remove or comment out the strip.gamma32() calls
in each effect function (see solidHue(),
pulseHue(), chaseHue(),
rainbowFade(), fireEffect(),
whiteFastFlicker(), and whiteLight()).
This project can be easily ported to other boards (Arduino Uno, ESP32, ESP8266, etc.):
Step 1: Adjust pin assignments in src/main.cpp (lines 5-14):
LED_PIN- Any PWM-capable digital pin (not actually used for PWM, but good practice)BUTTON_PIN- Any digital pin with interrupt capability recommendedPOT_PIN_*- Any analog input pins (A0-A7 on most boards)
Step 2: Board-specific setup:
Using PlatformIO:
- Edit
platformio.iniand change theboardline:[env:uno] board = uno ; or esp32dev, nodemcuv2, etc.
- PlatformIO will automatically handle board-specific compilation
Using Arduino IDE:
- Select your board from Tools → Board
- Verify pin assignments are valid for your board (check pinout diagram)
- Upload as normal
Important considerations:
- Memory: Boards with less than 2KB SRAM may struggle (Arduino Uno works fine)
- Voltage levels: ESP boards use 3.3V logic - use level shifter for 5V NeoPixels
- ADC resolution: ESP32 has 12-bit ADC (0-4095) instead of 10-bit - may need
to adjust
map()functions or useanalogReadResolution(10)on ESP32 - Pin capabilities: Not all pins support analog input or pull-up resistors
- Check wiring: One side to Pin 2, other to GND
- Test by shorting Pin 2 to GND manually
- Replace button if faulty
- Check Serial Monitor for button press messages
- ADC crosstalk: Code includes dummy reads and settling delays
- Hardware fixes:
- Add 0.1µF capacitor between each analog pin and GND (place close to Arduino pins, not at potentiometers)
- Ensure solid breadboard connections
- Use shorter wires
- Check ground connections
- Verify NeoPixel data pin (default: Pin 6)
- Check power supply (5V, sufficient current)
- Ensure common ground between Arduino and LED strip
- Test with simple Adafruit NeoPixel example first
- Open Serial Monitor to verify potentiometer readings
- Values should change from 0-1023 when turning pots
- If stuck at 0 or 1023, check wiring (5V, GND, center pin)
- Increment
NUM_EFFECTS(line 17) - Create effect function following existing patterns
- Add case to switch statement in
loop()(around line 186) - Add effect name to button press handler (around line 177)
Example skeleton:
void myNewEffect()
{
// Read potentiometers
uint8_t brightness = readBrightnessFromPot();
uint16_t hue = readHueFromPot();
uint8_t speed = readSpeedFromPot();
// Your effect logic here
strip.show();
delay(speed);
}- Built with Arduino and Adafruit NeoPixel library
- Designed for Arduino Nano with WS2812B LED strips
- I had some help from Claude Code while creating this project.
This project is released into the public domain under the Unlicense.
You are free to use, modify, distribute, and do whatever you want with this code, with no restrictions or obligations whatsoever. See the LICENSE file for details.
Enjoy your customizable LED effects! 💡✨
