Sometimes all a good Pycom project needs is some colourful lights! This tutorial demonstrates how to control WS2812 LEDs with the Pycom Wipy and Lopy4. WS2812 or WS2812B LEDs (otherwise known as NeoPixels) are the most commonly used RGB LEDs on the market, because of their low cost and ease of use. This MicroPython Library for the WS2812 will allow you to finally shine some colourful light on your Pycom Project!
Parts Required for this project:
- A Pycom Wipy or Lopy4 (this should work on other boards as well)
- A Pycom Expansion Board 3.0
- A Level Shifter
- Some WS2812 or Neopixel LEDs
- A 2.1mm Barrel Jack to Terminal Block (optional)
- A 5V Power Supply (optional)
Creating the Circuit
Pycom boards use 3.3V logic, and WS2812 LEDs use 5V logic. We have found that WS2812 will often run with 3.3V data. The same goes for powering the LEDs, they are made to run on 5V, but will happily run on 3.3V power (just a bit dimmer).
We have made our circuit using a Logic Level Shifter to convert the 3.3V signal into a 5V signal that is more ideal for the LEDs. Go ahead and try to wire your LEDs directly to your Pycom board without the level shifter and see if it works, any flickering or unusual LED behaviour would indicate that your LEDs require a level shifter.
For testing, we will be powering the Pycom and LEDs from the USB port, but in practice, you always want to power your LEDs directly from the power supply, so you don’t draw too much current through the Expansion Board. WS2812/NeoPixels draw about 60mA per LED when white and at full brightness. Under normal conditions, they will typically draw about 20mA per LED. This adds up quite a lot of current very quickly, be sure to use an appropriate power supply.
The Code
You can find the latest version of this code at our GitHub Repository:
Clone directly into Atom with this link: https://github.com/CoreElectronics/pycom-ws2812.git
The library is based on the MicroPython Library created by @JanBednarik, with Lopy functionality added by @aureleq. We updated the library to work with the Lopy4 and current versions of the Wipy.
We then went one step further and included a bunch of animation functions in the main file to make it easier to get dynamic lighting effects from your LEDs. People who have used the NeoPixel Library for Arduino will find these very familiar!
Set-Up and Test
First, change the numLed to reflect the number of LEDs you are driving, then set the brightness and the pin that the LEDs are connected to. If you are using the Wipy then it must be pin “P11” (G22 on the Expansion Board). On the Lopy4 you are able to change the pin assignment to other pins.
# set the number of LEDs numLed = 43
# initialize LEDs chain = WS2812( ledNumber=numLed, brightness=10, dataPin='P11' ) # dataPin is for LoPy board only
main.py Code
from ws2812 import WS2812 import utime import uos import pycom # disable LED heartbeat (probably not needed in this application) pycom.heartbeat(False) # set the number of LEDs numLed = 43 # initialize LEDs chain = WS2812( ledNumber=numLed, brightness=10, dataPin='P11' ) # dataPin is for LoPy board only # initialize LEDs Off data = [(0,0,0)] * numLed ####################################################### ### Animation Functions -- edit at your own risk! ### ### Scroll down for the Main Loop to edit ### ####################################################### # Input a value 0 to 255 to get a color value. # The colours are a transition r - g - b - back to r. # Do not change this! def Wheel(WheelPos): WheelPos = 255 - WheelPos if WheelPos < 85: return (255 - WheelPos * 3, 0, WheelPos * 3) if WheelPos < 170: WheelPos -= 85 return (0, WheelPos * 3, 255 - WheelPos * 3) WheelPos -= 170 return (WheelPos * 3, 255 - WheelPos * 3, 0) # Cycles all the lights through rainbow colors def rainbow(wait): for j in range (0,256,1): for i in range (0,numLed,1): data[i] = Wheel((i+j & 255)) chain.show( data ) utime.sleep_ms(wait) # Slightly different, this makes the rainbow equally distributed throughout def rainbowCycle(wait): for j in range (0,256,1): for i in range (0,numLed,1): data[i] = Wheel(int((i * 256 / numLed) + j) & 255) chain.show( data ) utime.sleep_ms(wait) # Fill the dots one after the other with a color def colorWipe(c, wait): for i in range(0, numLed, 1): data[i] = c chain.show( data ) utime.sleep_ms(wait) # Theatre-style crawling lights. def theaterChase(c, wait): for j in range(0, 10, 1): # do 10 cycles of chasing for q in range(0, 3, 1): for i in range(0, numLed, 3): try: data[i+q] = c # turn every third pixel on except: # if i+q is out of the list then ignore pass chain.show( data ) utime.sleep_ms(wait) for i in range(0, numLed, 3): try: data[i+q] = (0,0,0) # turn every third pixel off except: # if i+q is out of the list then ignore pass def theaterChaseRainbow(wait): for j in range(0, 256, 1): # cycle all 256 colors in the wheel for q in range(0, 3, 1): for i in range(0, numLed, 3): try: data[i+q] = Wheel((i + j) % 255) #Wheel( int((i+j)) % 255)) # turn every third pixel on except: # if i+q is out of the list then ignore pass chain.show( data ) utime.sleep_ms(wait) for i in range(0, numLed, 3): try: data[i+q] = (0,0,0) # turn every third pixel off except: # if i+q is out of the list then ignore pass # Fill the dots one after the other with a color def scrollWipe(wait): for j in range(0, 256, 16): # Transition through all colors of the wheel skip every 16 so the change is visible for i in range(0, numLed, 1): data[i] = Wheel((j) & 255) chain.show( data ) utime.sleep_ms(wait) # sparkle the LEDs to the set color def sparkle(c, wait): pixel = int.from_bytes(os.urandom(1), "big") % numLed pixel2 = int.from_bytes(os.urandom(1), "big") % numLed data[pixel] = c data[pixel2] = c chain.show( data ) utime.sleep_ms(wait) data[pixel] = (0,0,0) data[pixel2] = (0,0,0) # Fade the brightness up down and update a brightness parameter for other modes. def fade(c, wait): # Increases brightness for i in range(0, 50, 1): chain.set_brightness(i) # Slows brightness change when dim #if i
ws2812.py Code
# Based on https://github.com/JanBednarik/micropython-ws2812 # Adapted for LoPy by @aureleq, lopy4 updates by Stephen Haynes import gc from machine import SPI from machine import Pin from machine import disable_irq from machine import enable_irq from uos import uname class WS2812: """ Driver for WS2812 RGB LEDs. May be used for controlling single LED or chain of LEDs. Example of use: chain = WS2812( ledNumber=4, brightness=10, dataPin='P11' ) data = [ (255, 0, 0), # red (0, 255, 0), # green (0, 0, 255), # blue (85, 85, 85), # white ] chain.show(data) """ # Values to put inside SPI register for each color's bit buf_bytes = (b'\xE0\xE0', # 0 0b00 b'\xE0\xFC', # 1 0b01 b'\xFC\xE0', # 2 0b10 b'\xFC\xFC') # 3 0b11 def __init__(self, ledNumber=1, brightness=100, dataPin='P11'): """ Params: * ledNumber = count of LEDs * brightness = light brightness (integer : 0 to 100%) * dataPin = pin to connect data channel (LoPy4 only) """ self.ledNumber = ledNumber self.brightness = brightness # Prepare SPI data buffer (8 bytes for each color) self.buf_length = self.ledNumber * 3 * 8 self.buf = bytearray(self.buf_length) # SPI init # Bus 0, 8MHz => 125 ns by bit, 8 clock cycle when bit transfert+2 clock cycle between each transfert # => 125*10=1.25 us required by WS2812 if uname().sysname == 'LoPy4': self.spi = SPI(0, SPI.MASTER, baudrate=8000000, polarity=0, phase=1, pins=(None, dataPin, None)) # Enable pull down (not working for LoPy4) # Pin(dataPin, mode=Pin.OUT, pull=Pin.PULL_DOWN) else: #WiPy self.spi = SPI(0, SPI.MASTER, baudrate=8000000, polarity=0, phase=1) # Enable pull down # Pin('P11', mode=Pin.OUT, pull=Pin.PULL_DOWN) # Turn LEDs off self.show([]) def show(self, data): """ Show RGB data on LEDs. Expected data = [(R, G, B), ...] where R, G and B are intensities of colors in range from 0 to 255. One RGB tuple for each LED. Count of tuples may be less than count of connected LEDs. """ self.fill_buf(data) self.send_buf() def send_buf(self): """ Send buffer over SPI. """ disable_irq() self.spi.write(self.buf) enable_irq() gc.collect() def update_buf(self, data, start=0): """ Fill a part of the buffer with RGB data. Order of colors in buffer is changed from RGB to GRB because WS2812 LED has GRB order of colors. Each color is represented by 4 bytes in buffer (1 byte for each 2 bits). Returns the index of the first unfilled LED Note: If you find this function ugly, it's because speed optimisations beated purity of code. """ buf = self.buf buf_bytes = self.buf_bytes brightness = self.brightness index = start * 24 for red, green, blue in data: red = int(red * brightness // 100) green = int(green * brightness // 100) blue = int(blue * brightness // 100) buf[index:index+2] = buf_bytes[green >> 6 & 0b11] buf[index+2:index+4] = buf_bytes[green >> 4 & 0b11] buf[index+4:index+6] = buf_bytes[green >> 2 & 0b11] buf[index+6:index+8] = buf_bytes[green & 0b11] buf[index+8:index+10] = buf_bytes[red >> 6 & 0b11] buf[index+10:index+12] = buf_bytes[red >> 4 & 0b11] buf[index+12:index+14] = buf_bytes[red >> 2 & 0b11] buf[index+14:index+16] = buf_bytes[red & 0b11] buf[index+16:index+18] = buf_bytes[blue >> 6 & 0b11] buf[index+18:index+20] = buf_bytes[blue >> 4 & 0b11] buf[index+20:index+22] = buf_bytes[blue >> 2 & 0b11] buf[index+22:index+24] = buf_bytes[blue & 0b11] index += 24 return index // 24 def fill_buf(self, data): """ Fill buffer with RGB data. All LEDs after the data are turned off. """ end = self.update_buf(data) # Turn off the rest of the LEDs buf = self.buf off = self.buf_bytes[0] for index in range(end * 24, self.buf_length): buf[index:index+2] = off index += 2 def set_brightness(self, brightness): """ Set brighness of all leds """ self.brightness = brightness
Going Further
That’s all you need to get your LED project off the ground with Pycom! This is just an exhaustive example to help get you started though! If you want to learn more about Pycom, check out our Pycom Tutorials category, and turn your project into the IoT device it deserves to be! If you are using the Pycom Lopy4, then check out our The Things Network Tutorials. They walk you through everything you need to get connected to TTN using LoRaWAN!
Are you looking to add WS2812/NeoPixel LEDs to your other project but you aren't using Pycom? Check out our other guides to get you started on other hardware types: