├── .gitignore ├── LICENSE ├── README.md ├── example_240_leds.py ├── example_advanced.py ├── example_simple.py └── ws2812.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | *.sw[op] 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jan Bednarik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MicroPython WS2812 driver 2 | ========================= 3 | 4 | MicroPython driver for WS2812, WS2812B, and compatible RGB LEDs. These are 5 | popular RGB LEDs used for example in AdaFruit NeoPixels rings, strips, boards, 6 | etc. 7 | 8 | Driver has been tested with up to 240 LEDs (4m of NeoPixels stripe) but it 9 | should work with much more LEDs. 10 | 11 | Installation 12 | ------------ 13 | 14 | Copy `ws2812.py` file to your pyboard. 15 | 16 | Usage 17 | ----- 18 | 19 | ``` 20 | from ws2812 import WS2812 21 | chain = WS2812(spi_bus=1, led_count=4) 22 | data = [ 23 | (255, 0, 0), # red 24 | (0, 255, 0), # green 25 | (0, 0, 255), # blue 26 | (85, 85, 85), # white 27 | ] 28 | chain.show(data) 29 | ``` 30 | 31 | There are files `example_simple.py` and `example_advanced.py` prepared for 32 | NeoPixels ring (or similar) with 16 RGB LEDs. If you have it connected to SPI 33 | bus 1 then just copy `example_simple.py` or `example_advanced.py` as `main.py` 34 | to your pyboard and reset your pyboard. 35 | 36 | Video of `example_advanced.py` in action: http://youtu.be/ADYxiG40UJ0 37 | 38 | `example_240_leds.py` are some animations for 4 meters of NeoPixels strip with 39 | 240 RGB LEDs. In action video: http://youtu.be/vb5l3h1-TqA 40 | 41 | Wiring 42 | ------ 43 | 44 | WS2812 driver is using SPI bus. Connect your LED's input wire to the SPI bus 1 45 | MOSI (pin X8 on pyboard) or SPI bus 2 MOSI (pin Y8 on pyboard). Connect LED's 46 | power and ground wires to VIN and GND on pyboard. The same applies for LED 47 | rings, stripes, etc. (they have always one input wire). 48 | 49 | USB may be insufficient for powering lots of RGB LEDs. You may need to use 50 | additional power source. 51 | 52 | More info & Help 53 | ---------------- 54 | 55 | You can check more about the MicroPython project here: http://micropython.org 56 | 57 | Discussion about this driver: http://forum.micropython.org/viewtopic.php?f=5&t=394 58 | 59 | Changelog 60 | --------- 61 | 62 | * 1.3 - Allow updating only part of the buffer; re-add send_buf 63 | * 1.2 - Disable IRQ feature removed. (It's not neccesary in newer versions of 64 | MicroPython.) 65 | * 1.1 - Speed optimisations. 66 | * 1.0 - First release. 67 | -------------------------------------------------------------------------------- /example_240_leds.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This example shows some animations on 4 meters of NeoPixels strip with 240 LEDs. 5 | """ 6 | 7 | 8 | try: 9 | import pyb 10 | except ImportError: 11 | import machine as pyb 12 | import math 13 | 14 | from ws2812 import WS2812 15 | 16 | 17 | def color_gen(i=0): 18 | while True: 19 | i += 1 20 | red = (1 + math.sin(i * 0.1)) * 127 + 1 21 | green = (1 + math.sin(i * 0.1324)) * 127 + 1 22 | blue = (1 + math.sin(i * 0.1654)) * 127 + 1 23 | 24 | total = red + green + blue 25 | red = int(red / total * 255) 26 | green = int(green / total * 255) 27 | blue = int(blue / total * 255) 28 | 29 | yield red, green, blue 30 | 31 | colors = color_gen() 32 | 33 | 34 | def animation_1(led_count): 35 | data = [(0, 0, 0) for i in range(led_count)] 36 | step = 0 37 | while True: 38 | data[step % led_count] = next(colors) 39 | yield data 40 | step += 1 41 | 42 | 43 | def animation_2(led_count, offset=3, length=1): 44 | data = [(0, 0, 0) for i in range(led_count)] 45 | offsets = range(0, led_count, offset) 46 | step = 0 47 | while True: 48 | pos = step % led_count 49 | rgb = next(colors) 50 | for off in offsets: 51 | data[pos - off] = rgb 52 | data[pos - off - length] = (0, 0, 0) 53 | yield data 54 | step += 1 55 | 56 | 57 | def animation_3(led_count, offset=10): 58 | data = [(0, 0, 0) for i in range(led_count)] 59 | offsets = range(0, led_count, offset) 60 | step = 0 61 | while True: 62 | pos = step % led_count 63 | rgb = next(colors) 64 | for off in offsets: 65 | data[pos - off] = rgb 66 | yield data 67 | step += 1 68 | 69 | 70 | stripe = WS2812(spi_bus=1, led_count=240, intensity=0.05) 71 | 72 | anim_2 = animation_2(stripe.led_count) 73 | anim_3 = animation_3(stripe.led_count) 74 | anim_4 = animation_2(stripe.led_count, 15, 5) 75 | 76 | while True: 77 | anim_1 = animation_1(stripe.led_count) 78 | for i in range(240): 79 | stripe.show(next(anim_1)) 80 | 81 | for i in range(120): 82 | stripe.show(next(anim_2)) 83 | pyb.delay(50) 84 | 85 | for i in range(240): 86 | stripe.show(next(anim_3)) 87 | 88 | for i in range(240): 89 | stripe.show(next(anim_4)) 90 | -------------------------------------------------------------------------------- /example_advanced.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | try: 4 | import pyb 5 | except ImportError: 6 | import machine as pyb 7 | 8 | import math 9 | 10 | from ws2812 import WS2812 11 | 12 | 13 | ring = WS2812(spi_bus=1, led_count=16, intensity=0.1) 14 | 15 | 16 | def data_generator(led_count): 17 | data = [(0, 0, 0) for i in range(led_count)] 18 | step = 0 19 | while True: 20 | red = int((1 + math.sin(step * 0.1324)) * 127) 21 | green = int((1 + math.sin(step * 0.1654)) * 127) 22 | blue = int((1 + math.sin(step * 0.1)) * 127) 23 | data[step % led_count] = (red, green, blue) 24 | yield data 25 | step += 1 26 | 27 | 28 | for data in data_generator(ring.led_count): 29 | ring.show(data) 30 | pyb.delay(100) 31 | -------------------------------------------------------------------------------- /example_simple.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from ws2812 import WS2812 4 | 5 | 6 | ring = WS2812(spi_bus=1, led_count=16) 7 | 8 | data = [ 9 | (24, 0, 0), 10 | (0, 24, 0), 11 | (0, 0, 24), 12 | (12, 12, 0), 13 | (0, 12, 12), 14 | (12, 0, 12), 15 | (24, 0, 0), 16 | (21, 3, 0), 17 | (18, 6, 0), 18 | (15, 9, 0), 19 | (12, 12, 0), 20 | (9, 15, 0), 21 | (6, 18, 0), 22 | (3, 21, 0), 23 | (0, 24, 0), 24 | (8, 8, 8), 25 | ] 26 | 27 | ring.show(data) 28 | -------------------------------------------------------------------------------- /ws2812.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import gc 4 | try: 5 | import pyb 6 | except ImportError: 7 | import machine as pyb 8 | 9 | 10 | class WS2812: 11 | """ 12 | Driver for WS2812 RGB LEDs. May be used for controlling single LED or chain 13 | of LEDs. 14 | 15 | Example of use: 16 | 17 | chain = WS2812(spi_bus=1, led_count=4) 18 | data = [ 19 | (255, 0, 0), # red 20 | (0, 255, 0), # green 21 | (0, 0, 255), # blue 22 | (85, 85, 85), # white 23 | ] 24 | chain.show(data) 25 | 26 | Version: 1.0 27 | """ 28 | buf_bytes = (0x88, 0x8e, 0xe8, 0xee) 29 | 30 | def __init__(self, spi_bus=1, led_count=1, intensity=1): 31 | """ 32 | Params: 33 | * spi_bus = SPI bus ID (1 or 2) 34 | * led_count = count of LEDs 35 | * intensity = light intensity (float up to 1) 36 | """ 37 | self.led_count = led_count 38 | self.intensity = intensity 39 | 40 | # prepare SPI data buffer (4 bytes for each color) 41 | self.buf_length = self.led_count * 3 * 4 42 | self.buf = bytearray(self.buf_length) 43 | 44 | # SPI init 45 | self.spi = pyb.SPI(spi_bus, pyb.SPI.MASTER, baudrate=3200000, polarity=0, phase=1) 46 | 47 | # turn LEDs off 48 | self.show([]) 49 | 50 | def show(self, data): 51 | """ 52 | Show RGB data on LEDs. Expected data = [(R, G, B), ...] where R, G and B 53 | are intensities of colors in range from 0 to 255. One RGB tuple for each 54 | LED. Count of tuples may be less than count of connected LEDs. 55 | """ 56 | self.fill_buf(data) 57 | self.send_buf() 58 | 59 | def send_buf(self): 60 | """ 61 | Send buffer over SPI. 62 | """ 63 | self.spi.send(self.buf) 64 | gc.collect() 65 | 66 | def update_buf(self, data, start=0): 67 | """ 68 | Fill a part of the buffer with RGB data. 69 | 70 | Order of colors in buffer is changed from RGB to GRB because WS2812 LED 71 | has GRB order of colors. Each color is represented by 4 bytes in buffer 72 | (1 byte for each 2 bits). 73 | 74 | Returns the index of the first unfilled LED 75 | 76 | Note: If you find this function ugly, it's because speed optimisations 77 | beated purity of code. 78 | """ 79 | 80 | buf = self.buf 81 | buf_bytes = self.buf_bytes 82 | intensity = self.intensity 83 | 84 | mask = 0x03 85 | index = start * 12 86 | for red, green, blue in data: 87 | red = int(red * intensity) 88 | green = int(green * intensity) 89 | blue = int(blue * intensity) 90 | 91 | buf[index] = buf_bytes[green >> 6 & mask] 92 | buf[index+1] = buf_bytes[green >> 4 & mask] 93 | buf[index+2] = buf_bytes[green >> 2 & mask] 94 | buf[index+3] = buf_bytes[green & mask] 95 | 96 | buf[index+4] = buf_bytes[red >> 6 & mask] 97 | buf[index+5] = buf_bytes[red >> 4 & mask] 98 | buf[index+6] = buf_bytes[red >> 2 & mask] 99 | buf[index+7] = buf_bytes[red & mask] 100 | 101 | buf[index+8] = buf_bytes[blue >> 6 & mask] 102 | buf[index+9] = buf_bytes[blue >> 4 & mask] 103 | buf[index+10] = buf_bytes[blue >> 2 & mask] 104 | buf[index+11] = buf_bytes[blue & mask] 105 | 106 | index += 12 107 | 108 | return index // 12 109 | 110 | def fill_buf(self, data): 111 | """ 112 | Fill buffer with RGB data. 113 | 114 | All LEDs after the data are turned off. 115 | """ 116 | end = self.update_buf(data) 117 | 118 | # turn off the rest of the LEDs 119 | buf = self.buf 120 | off = self.buf_bytes[0] 121 | for index in range(end * 12, self.buf_length): 122 | buf[index] = off 123 | index += 1 124 | --------------------------------------------------------------------------------