树莓派40/100 - Pico控制WS2812B,一根信号线实现多种LED灯光效果(1)

本文介绍了如何使用树莓派Pico通过PIO(Programmable IO)和状态机控制WS2812B彩灯,展示了如何编写汇编代码来实现彩灯的各种效果,如颜色循环和追逐。通过理解WS2812B的数据通讯协议,可以实现单线控制多个LED灯珠,并通过修改代码实现不同的颜色模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从某多平台花了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, time
from machine import Pin
import rp2

# Configure the number of WS2812 LEDs.
NUM_LEDS = 30
PIN_NUM = 15
brightness = 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 pin
sm = 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 < 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)
 
 
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 array
import utime
import machine
import rp2

NUM_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 << 16 | b
    sm.put(ar, 8)
    utime.sleep_ms(20)

# Fade out.
for _ in range(24):
    for j in range(NUM_LEDS):
        ar[j] >>= 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开发系列文章

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

申龙斌

撸代码来深夜,来杯咖啡钱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值