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:

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.

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.



Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our newsletter!

25 thoughts 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.

    Reply
  2. I am new to micropython. Is it possible to use one button to cycle through the different effects rather than using 4 buttons? Any guide?

    Reply
  3. Worth to mention that you may get away with controlling an 5V strip with a 3.3V I/O pin, but it is batch-dependant and your next LED strip may not work at all, all would have strange issues. The issue is, TTL high level is 70% of the Vcc, so it’s officially 3.5V. Anything below that may treated as LOW and it would be normal. Usually you can get away with it with short wires and small number of LEDs, but as things get bigger, your troubles will start to emerge. To be on the safe side, you have to use a level translator of some kind.

    Reply
  4. Complete starter here. I couldn’t get passed WS2812 RGB LEDs Lighting Effects – Clear all pixels. Where in the code do you suppose to copy and past the code snips to get the code snips working?

    Reply
  5. Hi guys.
    I tried to use esp32 to control my ws2812b, which has 72 pixels for one meter long. However, the result is quite disappointing, I couldn’t change the color in to white or anything. It seems that my pixels were possessed by something maybe, lol. I have checked so many times that the GPIO, the codes and the connection way.
    I really hope you could help me to deal with that.
    Regards,
    Mike

    Reply
    • Hi.
      With 72 pixels long, you may need to have a separate power supply for the strip.
      You may also consider using a 5V to 3.3V level shifter.
      Regards,
      Sara

      Reply
  6. It’s the other way around…you have to convert the 3.3V output of the ESP32 to the 5V levels of the LED strip.
    …and I agree..there’s no chance you can run 72 LEDs from the ESP’s power supply, you’ll need a separate 5V supply.

    Reply
    • Thanks a lot, Attila.
      Sure, from the beginning I were aware that. Yet the lights still wouldn’t follow my order.

      Regards,
      Mike

      Reply
      • Hi Mike!

        What’s your solution for the level shift? I have an exact same project (ESP+addressable LEDs) and it’s working fine.
        I am using an N-channel BS170 mosfet to shift from 3.3 to 5V. You have to have a pullup resistor to 5V on the LEDs data pin, and then pulling it down to GND with the mosfet.

        Reply
        • Hi Attila!
          Thanks your reply.
          I didn’t use the same project as the demostration and I cut ”button part”, only trying to change the color for my own projects. With the esp32, I used the GPIO 26 as my Pin.OUT, and I added the power supply part–5v. Then I connected those together for having the same Positive and Negative poles.

          Right now, I copied all the stuff demostrated by the tutorial –”https://raw.githubusercontent.com/RuiSantosdotme/Random-Nerd-Tutorials/master/Projects/ESP-MicroPython/esp32_esp8266_ws2812b.py”.

          So in order to be the same page with my own one, I just changed the last orders into blow:
          “while True:
          clear()
          rainbow_cycle(200)”

          But the result is still negative.

          Reply
          • Hi Mike !

            I’m glad it works now. Was it a software bug or something with the hardware?

            This tutorial relies on the chance that the LEDs will work with 3.3V logic level (which is true for most of the time), but adding more and
            more LEDs will make it more and more marginal. I would still recommend you to use a level shifter between the ESP and the LEDs.

            Attila

  7. Thanks Sara,
    I know that, but the problem still kept troubling me. Maybe i can show you some pics if you email or anything.

    Regards,
    Mike

    Reply
  8. Thanks Santos and Sara, I made this addressable LED strip montage and it works fine (ESP32 with an 8 LED strip), and everything is very well explained.

    Reply
  9. The LEDs do not light.

    The Shell displays the error message:

    Unable to connect to COM5: could not open port ‘COM5’: PermissionError(13, ‘Access is denied.’, None, 5)

    If you have serial connection to the device from another program, then disconnect it there first.

    Device Manager reports a COM5 at 11500 kbps, I lowered it to 9600 in Thonny

    Reply
    • After thinking about this for a while I decided to try reinstalling the USB-to-Serial driver. It was installed when I was using the Arduino IDE and C++. That seems to have worked, but we’ll see.

      The WS2812B is not lighting up. To help me debug the issue, rather than use the WS2812 LED strip I used the onboard LEDs.

      #for Microcontroller NodeMCU 12E ESP8266 Board, the version I have that includes wifi capability

      #The NodeMCU has two on-board LEDs
      #The first LED comes with the ESP-12E Module and is connected to GPIO 2 of ESP8266
      #The second LED is on the break-out board (near the CP2102 IC) and is connected to GPIO 16

      #The ESP8266 uses ‘inverse logic’ meaning that they turn on when the pins are 0 and turn
      #off when the pins are 1

      import time
      from machine import Pin
      led1=Pin(2,Pin.OUT) #create the first LED object on pin 2 and set it to output
      led2=Pin(16,Pin.OUT) #create the second LED object on pin 16 and set to output
      i = 0

      #Alternate the blinking of the LEDs every half second 40 times
      while i < 40:
      led1.value(1) #turn off
      led2.value(0) #turn on
      time.sleep(0.5)
      led1.value(0) #turn on
      led2.value(1) #turn off
      time.sleep(0.5)
      led1.value(1) #turn off
      led2.value(0) #turn on

      i = i + 1

      Since I have over 40 LEDs in the strip I’m going to try using an external 5 volt power supply.

      Reply
    • The problem was completely my fault. The UART to Serial driver was installed when I was using Arduino C++ and it appears that after re-flashing the ESP8266 with Micro Python I should have reinstalled the driver. Once I did that everything began working fine.

      Reply

Leave a Reply to Mike Cancel reply

Download our Free eBooks and Resources

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