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.
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:
# Complete project details at https://RandomNerdTutorials.com import machine, neopixel n = 48 p = 5 np = neopixel.NeoPixel(machine.Pin(p), n) np = (255, 0, 0) np = (125, 204, 223) np = (120, 153, 23) np = (255, 0, 153) np.write()
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 = (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.
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()
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.
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.
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:
- 4x pushbuttons (momentary switches)
- 4x 10kOhm resistors
- Jumper wires
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!
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.
You can also follow the next schematic diagram to wire the circuit for ESP32:
Follow the next schematic diagram if you’re using an EPS8266:
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)
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.
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.
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:
- [eBook] MicroPython Programming with 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.
25 thoughts on “MicroPython: WS2812B Addressable RGB LEDs with ESP32 and ESP8266”
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.
Like the tutorial. A bit long though.
Its supposed to be a complete guide for the WS2812B strip with micropython. So, it needs to be a bit long 🙂
I’m getting random flicker when lights are cleared any suggestions?
What pin are you using to control the lights?
Is it an appropriate one? You can see our ESP32 GPIO guide to see if you’re using a suitable pin: https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
When you use the effects, is it working properly?
Wow, thanks a lot!
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?
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.
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?
You can try this script to see all the effects in action.
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.
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.
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.
Thanks a lot, Attila.
Sure, from the beginning I were aware that. Yet the lights still wouldn’t follow my order.
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.
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:
But the result is still negative.
If it’s possible that I can send you my work through your email? Mine is [email protected].
Thanks Attila, really appreciate your reply, I have found my mistake. Right now, it’s working.
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.
I know that, but the problem still kept troubling me. Maybe i can show you some pics if you email or anything.
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.
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
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
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
led1.value(0) #turn on
led2.value(1) #turn off
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.
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.