This tutorial shows how to control WS2812B addressable RGB LEDs (neopixels) with the ESP32 and ESP8266 using MicroPython.

There is a built-in library in MicroPython that makes it extremely easy to control these LEDs: the neopixel library. We’ll show you how to control individual LEDs, create functions to produce awesome lighting effects, and build a simple project to illustrate how everything works.
This tutorial can be applied to any strip or PCB board that has WS2812B addressable RGB LEDs (neopixels) like:
- WS2812B addressable RGB LED Strips
- WS2812B addressable RGB LED Rings
- WS2812B addressable RGB LED PCB Sticks
In this tutorial we’ll control two addressable RGB LED rings, and one addressable LED stick wired in series.

Prerequisites
To follow this tutorial you need to have MicroPython firmware installed in your ESP32 or ESP8266. You also need an IDE to write and upload the code to your board. We suggest using Thonny IDE or uPyCraft IDE:
- Thonny IDE:
- uPyCraft IDE:
- Install uPyCraft IDE (Windows, Mac OS X, Linux)
- Flash/Upload MicroPython Firmware to ESP32 and ESP8266
Introducing WS2812B RGB LEDs
The WS2812B LEDs are addressable RGB LEDs that can be connected in series, and be controlled individually using just one digital pin of a microcontroller. These LEDs have an IC built right into the LED that make all of this possible.

You can solder several LED rings and sticks and they will behave as one piece. Each PCB has IN and OUT pins that make wiring very simple:

The following figure shows how our setup looks like after soldering the LEDs.

To wire the RGB LED strip to the ESP32 or ESP8266 is very simple. You need to apply 5V to the VCC pin, GND to GND and connect a GPIO to the Din (data) pin. We’ll connect the data pin to GPIO 5.
Controlling WS2812B RGB LEDs
There’s a built-in MicroPython module called neopixel to control WS2812B addressable LEDs. For example, the next script controls 4 individual pixels:
import machine, neopixel
n = 48
p = 5
np = neopixel.NeoPixel(machine.Pin(p), n)
np[0] = (255, 0, 0)
np[3] = (125, 204, 223)
np[7] = (120, 153, 23)
np[10] = (255, 0, 153)
np.write()
Importing libraries
First, import the neopixel and machine modules:
import machine, neopixel
Create a neopixel object
Set the number of pixels in your strip to the n variable:
n = 48
Save the GPIO number that will control the strip on the p variable:
p = 5
Create a NeoPixel object called np on the GPIO you’ve defined earlier and with the number of LEDs you’ve also defined:
np = neopixel.NeoPixel(machine.Pin(p), n)
Controlling individual pixels
After initializing the neopixel object, you can start controlling the LEDs. Controlling an individual pixel is very easy. You can think of the strip as an array with nelements (number of pixels in this sample strip). Then, we just need to set a color to a specific element. For example, to set the first pixel to red:
np[0] = (255, 0, 0)
The following figure may help you better understand how it works:

Then, use the write() method for the changes to take effect.
np.write()

WS2812 RGB LEDs Lighting Effects
Now that you know how to control individual pixels, you can make your own lighting effects. We’ll provide some functions (based on the library examples) that you can use in your own projects.
Clear all pixels
Clearing all pixels is the same as setting all pixels to (0, 0, 0) color.
def clear():
for i in range(n):
np[i] = (0, 0, 0)
np.write()

Set all pixels to the same color
In a similar way, to set all the pixels to the same color, you can use the following function that accepts as arguments, the r, g, and b color parameters .
def set_color(r, g, b):
for i in range(n):
np[i] = (r, g, b)
np.write()

Bounce effect
The bounce() function creates a bounce effect and accepts the r, g and b parameters to set the color, and the waiting time. The waiting time determines how fast the bouncing effect is.
def bounce(r, g, b, wait):
for i in range(4 * n):
for j in range(n):
np[j] = (r, g, b)
if (i // n) % 2 == 0:
np[i % n] = (0, 0, 0)
else:
np[n – 1 – (i % n)] = (0, 0, 0)
np.write()
time.sleep_ms(wait)
This effect shows an off pixel that runs through all the strip positions.

Cycle effect
The cycle effect works similarly to the bounce effect. There is a pixel on that runs through all the strip positions while the other pixels are off.
def cycle(r, g, b, wait):
for i in range(4 * n):
for j in range(n):
np[j] = (0, 0, 0)
np[i % n] = (r, g, b)
np.write()
time.sleep_ms(wait)

Moving raibow effect
To produce a moving rainbow effect, you need two functions. The wheel() function generates the rainbow color spectrum by varying each color parameter between 0 and 255.
def wheel(pos):
Input a value 0 to 255 to get a color value.
The colours are a transition r - g - b - back to r.
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (255 - pos * 3, pos * 3, 0)
if pos < 170:
pos -= 85
return (0, 255 - pos * 3, pos * 3)
pos -= 170
return (pos * 3, 0, 255 - pos * 3)
After that, use the rainbow_cycle() function that uses the results from the wheel() function to distribute the rainbow across the number of LEDs on your strip.
def rainbow_cycle(wait):
for j in range(255):
for i in range(n):
rc_index = (i * 256 // n) + j
np[i] = wheel(rc_index & 255)
np.write()
time.sleep_ms(wait)
This function accepts as argument the waiting time. The waiting time defines how fast the rainbow effect moves.

Knowing how these functions work, you can build your own projects to produce amazing lighting effects. To see how everything works together, in the next section we’ll build a simple project to control a bunch of addressable RGB LEDs.
WS2812B RGB LEDs with MicroPython: Project example
Here, we’ll build a simple circuit with 4 pushbuttons. It will make different lighting effects depending on the pushbutton pressed.

Parts Required
In this project we’re using two addressable RGB LED rings with different sizes and an addressable RGB LED stick. However, you can use an RGB LED strip or addressable RGB LEDs in other configurations.
- ESP32 (read Best ESP32 development boards) or ESP8266 (read Best ESP8266 development boards)
- Addressable RGB LEDs:
- Ring
- Stick
- Strip
- 4x pushbuttons (momentary switches)
- 4x 10kOhm resistors
- Breadboard
- Jumper wires
Schematic
In both ESP32 and ESP8266 we’ll wire the circuit as follows:
Button 1 | GPIO 14 |
Button 2 | GPIO 12 |
Button 3 | GPIO 13 |
Button 4 | GPIO 15 |
Addressable RGB LED data pin | GPIO 5 |
Note: you can chose any other digital pins, if needed.
ESP32
You can also follow the next schematic diagram to wire the circuit for ESP32:

ESP8266
Follow the next schematic diagram if you’re using an EPS8266:

Code
Upload the following code to your ESP32 or ESP8266 as main.py.
from machine import Pin
import machine, neopixel, time
# define interrupt handling functions
def button_handler(pin):
global button_pressed
button_pressed = pin
# configure pushbuttons as interrupts
button1 = Pin(15, Pin.IN)
button1.irq(trigger=Pin.IRQ_RISING, handler=button_handler)
button2 = Pin(14, Pin.IN)
button2.irq(trigger=Pin.IRQ_RISING, handler=button_handler)
button3 = Pin(12, Pin.IN)
button3.irq(trigger=Pin.IRQ_RISING, handler=button_handler)
button4 = Pin(13, Pin.IN)
button4.irq(trigger=Pin.IRQ_RISING, handler=button_handler)
button_pressed = button1
# LED strip configuration
# number of pixels
n = 48
# strip control gpio
p = 5
np = neopixel.NeoPixel(machine.Pin(p), n)
# FUNCTIONS FOR LIGHTING EFFECTS
# bounce
def bounce(r, g, b, wait):
for i in range(2 * n):
for j in range(n):
np[j] = (r, g, b)
if (i // n) % 2 == 0:
np[i % n] = (0, 0, 0)
else:
np[n - 1 - (i % n)] = (0, 0, 0)
np.write()
time.sleep_ms(wait)
# cycle
def cycle(r, g, b, wait):
for i in range(n):
for j in range(n):
np[j] = (0, 0, 0)
np[i % n] = (r, g, b)
np.write()
time.sleep_ms(wait)
# function to go through all colors
def wheel(pos):
# Input a value 0 to 255 to get a color value.
# The colours are a transition r - g - b - back to r.
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (255 - pos * 3, pos * 3, 0)
if pos < 170:
pos -= 85
return (0, 255 - pos * 3, pos * 3)
pos -= 170
return (pos * 3, 0, 255 - pos * 3)
# rainbow
def rainbow_cycle(wait):
for j in range(255):
for i in range(n):
rc_index = (i * 256 // n) + j
np[i] = wheel(rc_index & 255)
np.write()
time.sleep_ms(wait)
# turn off all pixels
def clear():
for i in range(n):
np[i] = (0, 0, 0)
np.write()
while True:
if button_pressed == button1:
clear()
elif button_pressed == button2:
bounce(23, 210, 15, 70)
elif button_pressed == button3:
cycle(123, 0, 154, 50)
elif button_pressed == button4:
rainbow_cycle(1)
How the Code Works
Continue reading this section if you want to learn how the code works. Otherwise, you can skip to the “Demonstration” section.
Start by importing the necessary libraries:
from machine import Pin
import machine, neopixel, time
The pushbuttons will be set as interrupts. So, we need to create an interrupt handling function that will run every time an interrupt happens – in this case, the button_handler() function.
def button_handler(pin):
global button_pressed
button_pressed = pin
The interrupt handling function has an input parameter (pin) in which an object of class Pin will be passed when the interrupt happens. This allows us to know which pin generated the interrupt.
In our example, the handle interrupt function is called button_handler and it saves the pushbutton that was pressed on the button_pressed variable.
def button_handler(pin):
global button_pressed
button_pressed = pin
Note: button_pressed is defined as a global variable, because we want it to be accessible throughout all the code, not just inside of the button_handler() function.
After that, configure the pushbuttons as interrupt pins:
button1 = Pin(15, Pin.IN)
button1.irq(trigger=Pin.IRQ_RISING, handler=button_handler)
button2 = Pin(14, Pin.IN)
button2.irq(trigger=Pin.IRQ_RISING, handler=button_handler)
button3 = Pin(12, Pin.IN)
button3.irq(trigger=Pin.IRQ_RISING, handler=button_handler)
button4 = Pin(13, Pin.IN)
button4.irq(trigger=Pin.IRQ_RISING, handler=button_handler)
By default, the button_pressed variable is equal to button1, because this button clears the LEDs and we want them to be off by default.
button_pressed = button1
Then, we create a neopixel object on GPIO 5, with 48 LEDs. You should change the n variable with the number of LEDs you are controlling.
n = 48 # number of pixels
p = 5 # strip control gpio
np = neopixel.NeoPixel(machine.Pin(p), n)
After that, we define the functions we’ve seen in a earlier section: bounce(),cycle(), wheel(), rainbow_cycle(),andclear().
In the while loop, we check which button was pressed and call a different function depending on the button pressed:
- Button 1: clears the strip (all neopixels off)
- Button 2: bounce effect
- Button 3:cycle effect
- Button 4: rainbow effect
while True:
if button_pressed == button1:
clear()
elif button_pressed == button2:
bounce(23, 210, 15, 10)
elif button_pressed == button3:
cycle(123, 0, 154, 20)
elif button_pressed == button4:
rainbow_cycle(1)
Note: you can change the arguments of the previous functions to set the LEDs in different colors or adjust the wait parameter to make the effect faster or slower.
Demonstration
After uploading the previous code to your ESP32 or ESP8266 as main.py, press the ESP Enable/Reset button to run the new code.
Press each pushbutton to produce different effects. You can watch the video below for a live demonstration:
Note: when you press the pushbutton to select an effect, it will only start when the effect that is running stops.
Wrapping Up
In this tutorial you’ve learned how to control WS2812B addressable RGB LEDs (rings, strips, or sticks). Controlling these LEDs with MicroPython is simple thanks to the neopixellibrary. You’ve also learn how to set multiple interrupts with MicroPython.
Now, you can apply the concepts learned in this tutorial in your own projects. For example, you can build a web server with different buttons that controls the LEDs remotely.
We have more tutorials about RGB LEDs that you may like:
Other tutorials about MicroPython and ESP32 and ESP8266:
- MicroPython – Getting Started with MQTT on ESP32/ESP8266
- MicroPython with ESP32 and ESP8266: Interacting with GPIOs
We hope you enjoyed this project and learned something new.
Thanks for reading
4 thoughts on “MicroPython: WS2812B Addressable RGB LEDs with ESP32 and ESP8266”