znlgis 博客

GIS开发与技术分享

第20章:WS2812 RGB 灯珠驱动

本章讲解 WS2812(NeoPixel)RGB 灯珠的工作原理、单总线数据协议与炫彩灯效编程。


20.1 WS2812 简介

20.1.1 特点

WS2812 是一种智能控制 LED:

  • 内置驱动 IC
  • 单线级联,无需寻址
  • 24 位颜色(RGB 各 8 位)
  • 低电压驱动(3.5-5.3V)

20.1.2 内部结构

┌───────────────────────────┐
│   WS2812B 内部结构        │
│  ┌─────┐  ┌─────────────┐ │
│  │ LED │  │ 控制芯片    │ │
│  │ R G │  │  数据锁存   │ │
│  │  B  │  │  PWM 驱动   │ │
│  └─────┘  └─────────────┘ │
│                           │
│  DIN ─→ 控制芯片 ─→ DOUT  │
└───────────────────────────┘

20.1.3 级联原理

MCU → LED1 → LED2 → LED3 → ...
      │       │       │
      DIN    DOUT   DIN    DOUT
           ↓            ↓
      接收 24 位后,剩余数据传递给下一个

20.2 数据协议

20.2.1 时序规范

WS2812 使用单总线协议,通过脉宽编码:

逻辑 "0":
┌────┐
│    │     高: 220-380ns
│    └────────────
         低: 580-1000ns
    总计约 1.25μs

逻辑 "1":
┌──────────┐
│          │  高: 580-1000ns
│          └────
              低: 580-1000ns
    总计约 1.25μs

复位信号:
────────────────────
    低电平 > 50μs

20.2.2 数据格式

每个 LED 接收 24 位数据:

┌────────┬────────┬────────┐
│ G[7:0] │ R[7:0] │ B[7:0] │
└────────┴────────┴────────┘
  绿色      红色     蓝色
  高位在前

注意:WS2812 是 GRB 顺序,不是 RGB!

20.2.3 数据传输过程

时间线:
MCU 发送: [LED1 数据 24位] [LED2 数据 24位] [LED3 数据 24位] [复位]
         │                │                │
LED1:    锁存 24位        │                │
                         │                │
LED2:                    锁存 24位         │
                                         │
LED3:                                    锁存 24位

20.3 MicroPython 驱动

20.3.1 使用 neopixel 库

'''
实验:WS2812 基础控制
接线:DIN 连接 GP16
'''
from machine import Pin
import neopixel
import time

# 创建 NeoPixel 对象
NUM_LEDS = 4
np = neopixel.NeoPixel(Pin(16), NUM_LEDS)

# 设置颜色 (R, G, B)
np[0] = (255, 0, 0)    # 红色
np[1] = (0, 255, 0)    # 绿色
np[2] = (0, 0, 255)    # 蓝色
np[3] = (255, 255, 0)  # 黄色

# 更新显示
np.write()

20.3.2 使用 PIO 状态机

Pico 可以使用 PIO 实现精确时序:

'''
实验:PIO 驱动 WS2812
'''
import array
import time
from machine import Pin
import rp2

@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()

class WS2812:
    def __init__(self, pin, num_leds, brightness=1.0):
        self.num_leds = num_leds
        self.brightness = brightness
        self.ar = array.array("I", [0] * num_leds)
        self.sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(pin))
        self.sm.active(1)
    
    def __setitem__(self, index, color):
        r, g, b = color
        r = int(r * self.brightness)
        g = int(g * self.brightness)
        b = int(b * self.brightness)
        self.ar[index] = (g << 16) | (r << 8) | b
    
    def __getitem__(self, index):
        val = self.ar[index]
        return ((val >> 8) & 0xFF, (val >> 16) & 0xFF, val & 0xFF)
    
    def fill(self, color):
        for i in range(self.num_leds):
            self[i] = color
    
    def write(self):
        for i in range(self.num_leds):
            self.sm.put(self.ar[i], 8)
        time.sleep_us(60)

# 使用
strip = WS2812(16, 4, brightness=0.5)
strip[0] = (255, 0, 0)
strip.write()

20.4 灯光效果

20.4.1 彩虹效果

'''
实验:彩虹流水效果
'''
import neopixel
from machine import Pin
import time

np = neopixel.NeoPixel(Pin(16), 8)

def wheel(pos):
    """色轮函数,返回 0-255 位置的颜色"""
    if pos < 85:
        return (255 - pos * 3, pos * 3, 0)
    elif pos < 170:
        pos -= 85
        return (0, 255 - pos * 3, pos * 3)
    else:
        pos -= 170
        return (pos * 3, 0, 255 - pos * 3)

def rainbow_cycle(wait_ms=20):
    """彩虹循环"""
    for j in range(256):
        for i in range(len(np)):
            rc_index = (i * 256 // len(np) + j) & 255
            np[i] = wheel(rc_index)
        np.write()
        time.sleep_ms(wait_ms)

while True:
    rainbow_cycle()

20.4.2 流水灯

'''
实验:流水灯效果
'''
def color_wipe(color, wait_ms=50):
    """逐个点亮"""
    for i in range(len(np)):
        np[i] = color
        np.write()
        time.sleep_ms(wait_ms)
    
    # 逐个熄灭
    for i in range(len(np)):
        np[i] = (0, 0, 0)
        np.write()
        time.sleep_ms(wait_ms)

while True:
    color_wipe((255, 0, 0), 100)
    color_wipe((0, 255, 0), 100)
    color_wipe((0, 0, 255), 100)

20.4.3 呼吸灯

'''
实验:全体呼吸效果
'''
import math

def breathing(color, cycles=3, speed=0.02):
    """呼吸灯"""
    r, g, b = color
    
    for _ in range(cycles):
        # 渐亮
        for i in range(100):
            brightness = (math.sin(i * math.pi / 100) ** 2)
            np.fill((int(r * brightness),
                    int(g * brightness),
                    int(b * brightness)))
            np.write()
            time.sleep(speed)
        
        # 渐暗(已包含在正弦波中)

breathing((255, 100, 0))

20.4.4 跑马灯

'''
实验:跑马灯效果
'''
def chase(color, bg_color=(0,0,0), wait_ms=50, width=1):
    """跑马灯"""
    for offset in range(len(np)):
        for i in range(len(np)):
            if i >= offset and i < offset + width:
                np[i] = color
            else:
                np[i] = bg_color
        np.write()
        time.sleep_ms(wait_ms)

while True:
    chase((255, 0, 0), width=2)
    chase((0, 255, 0), width=2)
    chase((0, 0, 255), width=2)

20.4.5 闪烁星星

'''
实验:随机闪烁效果
'''
import random

def twinkle(color, count=3, wait_ms=100):
    """随机闪烁"""
    for _ in range(20):
        # 关闭所有
        np.fill((0, 0, 0))
        
        # 随机点亮几个
        for _ in range(count):
            i = random.randint(0, len(np) - 1)
            brightness = random.uniform(0.3, 1.0)
            np[i] = (int(color[0] * brightness),
                    int(color[1] * brightness),
                    int(color[2] * brightness))
        
        np.write()
        time.sleep_ms(wait_ms)

twinkle((255, 255, 255))

20.5 颜色处理

20.5.1 HSV 颜色空间

def hsv_to_rgb(h, s, v):
    """
    HSV 转 RGB
    h: 色相 0-360
    s: 饱和度 0-1
    v: 明度 0-1
    """
    if s == 0:
        return (int(v * 255), int(v * 255), int(v * 255))
    
    h = h / 60
    i = int(h)
    f = h - i
    p = int(v * (1 - s) * 255)
    q = int(v * (1 - s * f) * 255)
    t = int(v * (1 - s * (1 - f)) * 255)
    v = int(v * 255)
    
    if i == 0:
        return (v, t, p)
    elif i == 1:
        return (q, v, p)
    elif i == 2:
        return (p, v, t)
    elif i == 3:
        return (p, q, v)
    elif i == 4:
        return (t, p, v)
    else:
        return (v, p, q)

# 使用 HSV 创建平滑彩虹
for hue in range(0, 360, 45):
    color = hsv_to_rgb(hue, 1, 0.5)
    print(f"色相 {hue}: RGB {color}")

20.5.2 亮度控制

def apply_brightness(color, brightness):
    """应用亮度系数"""
    return tuple(int(c * brightness) for c in color)

# 50% 亮度
dim_red = apply_brightness((255, 0, 0), 0.5)  # (127, 0, 0)

20.5.3 颜色渐变

def interpolate_color(color1, color2, factor):
    """
    颜色插值
    factor: 0.0 = color1, 1.0 = color2
    """
    return tuple(
        int(c1 + (c2 - c1) * factor)
        for c1, c2 in zip(color1, color2)
    )

# 红到蓝渐变
for i in range(11):
    color = interpolate_color((255, 0, 0), (0, 0, 255), i / 10)
    print(f"{i * 10}%: {color}")

20.6 实用应用

20.6.1 状态指示器

'''
实验:传感器状态可视化
'''
from machine import ADC

temp_sensor = ADC(4)  # 内部温度

def temperature_to_color(temp):
    """温度映射到颜色"""
    if temp < 20:
        return (0, 0, 255)    # 冷 - 蓝色
    elif temp < 25:
        return (0, 255, 0)    # 舒适 - 绿色
    elif temp < 30:
        return (255, 255, 0)  # 温暖 - 黄色
    else:
        return (255, 0, 0)    # 热 - 红色

while True:
    raw = temp_sensor.read_u16()
    voltage = raw * 3.3 / 65535
    temp = 27 - (voltage - 0.706) / 0.001721
    
    color = temperature_to_color(temp)
    np.fill(color)
    np.write()
    
    print(f"温度: {temp:.1f}°C")
    time.sleep(1)

20.6.2 音量指示条

'''
实验:LED 音量条
'''
from machine import ADC

sound = ADC(26)
NUM_LEDS = 8

def volume_bar(level):
    """音量条显示"""
    # level: 0-100
    lit_leds = int(level / 100 * NUM_LEDS)
    
    for i in range(NUM_LEDS):
        if i < lit_leds:
            if i < NUM_LEDS * 0.6:
                np[i] = (0, 255, 0)    # 绿
            elif i < NUM_LEDS * 0.85:
                np[i] = (255, 255, 0)  # 黄
            else:
                np[i] = (255, 0, 0)    # 红
        else:
            np[i] = (0, 0, 0)
    
    np.write()

baseline = sum(sound.read_u16() for _ in range(100)) // 100

while True:
    raw = sound.read_u16()
    level = min(100, abs(raw - baseline) / 500 * 100)
    volume_bar(level)
    time.sleep_ms(10)

20.7 本章小结

本章介绍了 WS2812 RGB 灯珠驱动:

  1. WS2812 特点
    • 内置驱动 IC,单线级联
    • 24 位颜色(GRB 顺序)
  2. 数据协议
    • 脉宽编码(高电平宽度区分 0/1)
    • 24 位/LED,自动传递
  3. 灯光效果
    • 彩虹、流水、呼吸、跑马灯
    • HSV 颜色空间便于编程
  4. 实用应用
    • 状态指示器
    • 音量可视化

下一章将学习超声波测距系统。