MicroPython: WS2812B Addressable RGB LEDs with ESP32 and ESP8266

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

ws2812b addressable rgb led micropython esp32 esp8266

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:

In this tutorial we’ll control two addressable RGB LED rings, and one addressable LED stick wired in series.

WS2812B addressable RGB LEDs Strips, rings, PCB sticks

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:

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.

addressable ws2812 rgb led neopixel

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:

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

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

ws2812b neopixel wired in series

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:

# Complete project details at https://RandomNerdTutorials.com

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()

View raw code

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:

ws2812b control individual pixels micropython

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

np.write()
ws2812b control individual pixels micropython

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.

Download our Free eBooks and Resources

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.

ws2812b led with esp32 esp8266 micropython circuit

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.

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

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:

esp32 neopixel pushbutton ws2812b project

ESP8266

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

esp8266 neopixel pushbutton ws2812b project

Code

Upload the following code to your ESP32 or ESP8266 as main.py.

# Complete project details at https://RandomNerdTutorials.com

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)

View raw code

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 neopixel library. 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:

We hope you enjoyed this project and learned something new.

Thanks for reading.


Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our weekly newsletter!

1 thought on “MicroPython: WS2812B Addressable RGB LEDs with ESP32 and ESP8266”

  1. Excellent tutorial, thanks! I will implement your examples as soon as my led strips arrive. It is much clearer now, at least.

    Maybe some more explanation or examples on the higher level functions (cycle, wheel, …) could be helpful for those who want to understand the math involved.

Leave a Comment

Download our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.