Skip to main content
Electronics

Getting Started with ESP32 and NeoPixels in CircuitPython

5 min read
Close-up of an ESP32 board with colorful NeoPixel LEDs glowing blue and green

NeoPixels are one of the most satisfying pieces of hardware you can wire up. You get a string of individually addressable, full-color LEDs that respond to a single data wire, and they’re dead simple to control from CircuitPython. This guide gets you from zero to a working animation loop in about 30 minutes.

What You’ll Need

  • ESP32-S3 Feather (Adafruit) or similar ESP32-S3 board with CircuitPython support
  • NeoPixel strip or ring — 8 to 16 pixels is a good starting count
  • 330Ω resistor — for the data line (more on this below)
  • 5V power supply — separate from your board for anything more than ~10 pixels
  • Breadboard, jumper wires, USB-C cable

I’m using the Adafruit ESP32-S3 Feather throughout this guide because it has native USB and CircuitPython support built in. Most of this applies equally to other ESP32-S3 boards with minor pin adjustments.

Setting Up CircuitPython

If your board doesn’t already have CircuitPython on it, the process is straightforward:

  1. Download the latest CircuitPython UF2 file for your board from circuitpython.org. Search for your specific board.
  2. Put your board into bootloader mode. On the ESP32-S3 Feather, double-tap the reset button. You should see a drive called FTHRS3BOOT (or similar) appear on your computer.
  3. Drag the .uf2 file onto that drive. The board will reboot automatically.
  4. After reboot, a drive called CIRCUITPY appears. That’s your board’s filesystem.

To install the NeoPixel library, download the CircuitPython Library Bundle from the same site and copy neopixel.mpy (and its dependency adafruit_pixelbuf.mpy) from the bundle’s lib/ folder into the CIRCUITPY/lib/ folder on your board.

Wiring It Up

Here’s how to connect a NeoPixel strip to the ESP32-S3 Feather:

NeoPixelBoard
5V (or VCC)USB 5V or external 5V supply
GNDGND
DIN (Data In)Pin D5 → through 330Ω resistor

A few notes on wiring that matter:

Always use the 330Ω resistor on the data line. It protects the first pixel from voltage spikes and edge ringing. Without it, you might get random color glitches or fry a pixel over time. Put it as close to the data input of the strip as possible.

Power the pixels separately for more than ~8–10 pixels. Each NeoPixel draws up to 60mA at full white brightness. At 16 pixels, that’s nearly 1A — more than your board’s 3.3V regulator was designed to supply. Connect a 5V 2A supply directly to the strip’s power rails, sharing only the ground with your board.

Create a file called code.py on the CIRCUITPY drive and paste this in:

import board
import neopixel

pixels = neopixel.NeoPixel(board.D5, 8, brightness=0.3)
pixels.fill((0, 200, 255))  # cyan

Save the file. CircuitPython auto-reloads whenever code.py changes, so your pixels should turn cyan immediately. If they don’t:

  • Check your wiring connections
  • Make sure the data line goes through the resistor
  • Verify the pixel count (second argument to NeoPixel) matches your strip

The brightness parameter is a float from 0.0 to 1.0. Start at 0.3 — NeoPixels at full brightness are uncomfortably bright and will exhaust a small power supply fast.

Color Wheel Animation

Now for the interesting part. The adafruit_led_animation library includes a color wheel function, but you can implement a simple one yourself:

import board
import neopixel
import time

NUM_PIXELS = 8
pixels = neopixel.NeoPixel(board.D5, NUM_PIXELS, brightness=0.3, auto_write=False)

def wheel(pos):
    """Generate rainbow colors across 0-255 positions."""
    if pos < 85:
        return (255 - pos * 3, pos * 3, 0)
    elif pos < 170:
        pos -= 85
        return (0, 255 - pos * 3, pos * 3)
    else:
        pos -= 170
        return (pos * 3, 0, 255 - pos * 3)

def rainbow_cycle(wait):
    for j in range(255):
        for i in range(NUM_PIXELS):
            pixel_index = (i * 256 // NUM_PIXELS) + j
            pixels[i] = wheel(pixel_index & 255)
        pixels.show()
        time.sleep(wait)

while True:
    rainbow_cycle(0.01)

Note the auto_write=False parameter. By default, NeoPixel writes to the strip on every assignment. With auto_write=False, you batch your changes and then call pixels.show() once — this prevents flickering and dramatically improves animation smoothness.

Common Gotchas

Wrong pin. board.D5 maps to a specific GPIO. If your board labels pins differently, check the pinout diagram for your exact board on the Adafruit learn site.

Pixels light up the wrong color or flicker randomly. Usually a power issue. Try lowering brightness to 0.2, or power the strip from a dedicated 5V supply.

Nothing happens at all. 99% of the time this is the data line. Check that you have the resistor in-line, your wire goes to the DIN pad (not DOUT), and there’s no cold solder joint.

CircuitPython throws ImportError: no module named neopixel. The library isn’t in CIRCUITPY/lib/. Double-check you copied both neopixel.mpy and adafruit_pixelbuf.mpy.

What’s Next

From here you can explore the adafruit_led_animation library for pre-built effects (comet, sparkle, rainbow), or start integrating capacitive touch to make your pixels respond to input. That’s exactly what I did with the Totem project — which I’ll be writing about in depth in the next few posts.

Happy hacking.