树莓派40/100 - Pico控制WS2812B,一根信号线实现多种LED灯光效果(1)
从某多平台花了15元钱买了一米长的WS2812B彩灯,用于我的Pico编程试验,这种灯的神奇之处在于只需一根信号线,能够控制串联在一起的30颗LED灯珠(好像能长达1024颗灯),实现各种彩灯效果。
接线非常简单,正极接5V(我的灯带只有1米长,用树莓派Pico供电并不吃力,如果比较长的灯带,需要额外电源),一端接地,树莓派GP15接信号线Din。灯带里面的Do不用管它,它实际上是Dout的意思,信号经过Din处理后,点亮那个灯,吃掉一些字节,再经过Dout出来,从而可以串联更多的灯。
官方文档《Raspberry Pi Pico Python SDK》里有一节介绍PIO控制WS2812灯的代码,直接抄过来,修改一下灯的个数,引脚编号PIN_NUM,代码虽然不理解,但程序可以马上运行。
# Example using PIO to drive a set of WS2812 LEDs.import array, timefrom machine import Pinimport rp2# Configure the number of WS2812 LEDs.NUM_LEDS = 30PIN_NUM = 15brightness = 0.2@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)def ws2812(): T1 = 2 T2 = 5 T3 = 3 wrap_target() label("bitloop") out(x, 1) .side(0) [T3 - 1] jmp(not_x, "do_zero") .side(1) [T1 - 1] jmp("bitloop") .side(1) [T2 - 1] label("do_zero") nop() .side(0) [T2 - 1] wrap()# Create the StateMachine with the ws2812 program, outputting on pinsm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))# Start the StateMachine, it will wait for data on its FIFO.sm.active(1)# Display a pattern on the LEDs via an array of LED RGB values.ar = array.array("I", [0 for _ in range(NUM_LEDS)])##########################################################################def pixels_show(): dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)]) for i,c in enumerate(ar): r = int(((c >> 8) & 0xFF) * brightness) g = int(((c >> 16) & 0xFF) * brightness) b = int((c & 0xFF) * brightness) dimmer_ar[i] = (g<<16) + (r<<8) + b sm.put(dimmer_ar, 8) time.sleep_ms(10)def pixels_set(i, color): ar[i] = (color[1]<<16) + (color[0]<<8) + color[2]def pixels_fill(color): for i in range(len(ar)): pixels_set(i, color)def color_chase(color, wait): for i in range(NUM_LEDS): pixels_set(i, color) time.sleep(wait) pixels_show() time.sleep(0.2) 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 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) def rainbow_cycle(wait): for j in range(255): for i in range(NUM_LEDS): rc_index = (i * 256 // NUM_LEDS) + j pixels_set(i, wheel(rc_index & 255)) pixels_show() time.sleep(wait)BLACK = (0, 0, 0)RED = (255, 0, 0)YELLOW = (255, 150, 0)GREEN = (0, 255, 0)CYAN = (0, 255, 255)BLUE = (0, 0, 255)PURPLE = (180, 0, 255)WHITE = (255, 255, 255)COLORS = (BLACK, RED, YELLOW, GREEN, CYAN, BLUE, PURPLE, WHITE)print("fills")for color in COLORS: pixels_fill(color) pixels_show() time.sleep(0.2)print("chases")for color in COLORS: color_chase(color, 0.01)print("rainbow")rainbow_cycle(0)
网上还有一个更精简的版本,便于慢慢理解程序的主要逻辑。
import arrayimport utimeimport machineimport rp2NUM_LEDS = 30@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)def ws2812(): T1 = 2 T2 = 5 T3 = 3 wrap_target() label("bitloop") out(x, 1) .side(0) [T3 - 1] jmp(not_x, "do_zero") .side(1) [T1 - 1] jmp("bitloop") .side(1) [T2 - 1] label("do_zero") nop() .side(0) [T2 - 1] wrap()# 建立状态机,设置输出针的编号sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=machine.Pin(15))# Start the StateMachine, it will wait for data on its FIFO.sm.active(1)# Display a pattern on the LEDs via an array of LED RGB values.ar = array.array("I", [0 for _ in range(NUM_LEDS)])# Cycle colours.for i in range(4 * NUM_LEDS): for j in range(NUM_LEDS): r = j * 100 // (NUM_LEDS - 1) b = 100 - j * 100 // (NUM_LEDS - 1) if j != i % NUM_LEDS: r >>= 3 b >>= 3 ar[j] = r <>= 1 sm.put(ar, 8) utime.sleep_ms(50)
看看视频效果:
https://v.qq.com/x/page/o33047uvo1e.html
树莓派Pico控制WS2812b彩色灯珠
这里用到了PIO(Programmable IO )的概念,可以嵌入汇编语句,细节还有待后面进一步的学习。
还有一个状态机的概念,暂时也不太理解,没关系,先抄着慢慢学。
再来学习一下WS2812B的数据通讯协议,看用户手册,有这样一段话:
数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。像素点采用自动整形转发技术,使得该像素点的级联个数不受信号传送的限制,仅受限信号传输速度要求。
可以看出,每经过一个像素点,吃掉24个二进制位,信号就是这样从头传到尾的。
这24个二进制位就是颜色的RGB值,不对,是GRB值,与电脑上常见的红绿蓝顺序有点不一样,所以可以在源代码里看到这样的写法:
(g<<16) + (r<<8) + b
说明书中提到的主要特点:
- IC控制电路与LED点光源共用一个电源。
- 控制电路与RGB芯片集成在一个5050封装的元器件中,构成一个完整的外控像素点。
- 内置信号整形电路,任何一个像素点收到信号后经过波形整形再输出,保证线路波形畸变不会累加。
- 内置上电复位和掉电复位电路。
- 每个像素点的三基色颜色可实现256级亮度显示,完成16777216种颜色的全真色彩显示。
- 端口扫描频率2KHz/s。
- 串行级联接口,能通过一根信号线完成数据的接收与解码。
- 任意两点传输距离在不超过5米时无需增加任何电路。
- 当刷新速率30帧/秒时,级联数不小于1024点。
- 数据发送速度可达800Kbps。
- 光的颜色高度一致,性价比高。
后记:
WS2812B里的PIO汇编代码,后来也读懂了,详细的解释说明在这里。
推荐阅读:
树莓派Pico开发系列文章