Skip to content

概述

WS2812 是一款内置驱动芯片的 RGB 灯珠(RGB = 红绿蓝三色,混在一起能调出任意颜色),只需要一根数据线就能把很多颗灯珠串联级联(第一颗的 DO 接第二颗的 DI,数据像接力一样一颗颗传下去)成灯带。本教程使用官方 demo_ws2812 示例(默认 SPI+DMA 驱动方式,数据脚 IO12),驱动 46 颗灯珠做红色 ↔ 蓝色平滑渐变,并演示单颗灯珠的颜色、亮度控制。

用大白话讲:WS2812 灯带就像圣诞树上的彩灯串——一串灯只有一根数据线,第一颗灯把数据里的颜色"领走",剩下的数据继续传给下一颗,所以串多长都只需要一根线。每颗灯珠发什么颜色是有顺序的:灯珠按 GRB(绿红蓝的排列顺序)收数据,你发「红」它先收到绿色位、再收红色位。本教程用 SPI 高速把颜色数据"喂"给灯带,再靠渐变算法让颜色从红慢慢过渡到蓝。

本教程基于安信可官方 SDKAi-Thinker-Open/Ai-Thinker-WB2,版本 release_bl_iot_sdk_1.6.40)的官方示例 applications/iot-solution/demo_ws2812 编写,代码可在本地 SDK 中直接找到。基础篇 SPI 协议 用的是 applications/peripherals/spi_ws2812 工程(8 颗灯珠、SPI 裸驱动);本页用 iot-solution 版本,封装了更完整的灯带驱动 API(components/stage/ws2812),直接改结构体参数即可适配任意灯珠数量。

🎯本页目标驱动 46 颗 WS2812 灯珠做红蓝平滑渐变,掌握级联接线与灯带驱动 API(单灯控制、亮度、渐变)。
🧰前置条件① Ai-WB2 开发板、WS2812 灯带/模块(至少 46 颗)、杜邦线 ② 已按 [SDK 安装](../sdk/sdk_intro) 完成开发环境搭建。
🔗相关章节SPI+DMA 原理见 [SPI 协议](../basic/spi) 与 [DMA 传输](../basic/dma);上篇 [SSD1306 OLED 显示屏](./ssd1306)。

硬件接线

按官方示例接线(默认 SPI 驱动方式,数据脚 IO12):

Ai-WB2 引脚 WS2812 灯带
IO12 DIN(数据输入)
5V VCC
GND GND

💡 串联级联:灯带上一颗灯珠的 DO(数据输出)接到下一颗的 DI(数据输入),数据从第一颗往后接力传递,所以不管多少颗灯都只需要一根数据线。接线时注意方向:DIN 接 IO12,别把 DO 当输入接错了。

⚡ 46 颗灯珠全亮时电流较大(每颗全白约 60mA),建议按官方接线 VCC 接 5V(灯带电源与开发板 GND 共地,共地 = 两边地线接在一起,否则电压没有参考点,通信会乱码)。

进入示例工程

打开终端进入官方 demo_ws2812 示例工程目录:

cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_ws2812

说明:cd 是「进入目录」命令,这里进入 demo_ws2812 工程目录;后续的 make 编译、make flash 烧录命令都必须先在这个目录里执行。

工程目录结构说明:

文件 作用
main/main.c 主程序源码,本教程主要修改的文件
proj_config.mk 工程配置,其中 CONFIG_WS2812_MODE:=SPI_MODE 指定驱动方式(默认 SPI,可改为 IR_MODE)
Makefile 编译入口,一般无需修改
编写代码

打开 main/main.c,本步完整代码已移至文末,见:

📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(applications/iot-solution/demo_ws2812/main/main.c)完全一致。

代码要点:

代码 作用
bl_sys_init() 初始化系统底层资源(时钟等),SDK 工程第一步都要调用
ws2812_init(&ws2812_strip) 按结构体(46 颗、亮度 0.05、IO12)初始化灯带,不告诉驱动有几颗灯它就没法分配内存
ws2812_set_pixel_color(0, 0xff, 0x00, 0x00) 把第 0 颗灯设为红色(r/g/b),库里已按 GRB 顺序排好,颜色不对就是顺序错了
ws2812_set_pixel_brightness(0, 0.5) 单独把第 0 颗灯亮度调到 50%,全局亮度 0.05 太暗时靠它提亮
ws2812_show_leds() 把内存里排好的颜色真正「发」给灯珠,没有它灯珠永远不会变色
smoothcolorTransition(RED, BLUE, 500, cb, NULL) 500 步把整条灯带从红渐变到蓝,每步回调里刷新一次,没有它灯色就静止不变
编译工程

在工程目录执行编译:

make -j8

说明:make 是「编译工程」命令,把代码变成开发板能运行的固件(程序文件);-j8 表示用 8 个核并行编译,速度更快。

编译成功后生成固件 build_out/demo_ws2812.bin

烧录固件

开发板保持 USB 连接,确认串口设备号(Linux 下通常为 /dev/ttyUSB0),执行烧录:

make flash p=/dev/ttyUSB0 b=921600

说明:make flash 是「烧录」命令,把编译好的固件写进开发板芯片。p= 后面是串口设备号(要改成你电脑上实际的串口,可用 ls /dev/ttyUSB* 查看),b= 是烧录波特率(传输速度)。

⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式(部分开发板自动进入),等待进度条完成即烧录成功。

运行验证

烧录完成后开发板自动重启运行,观察灯带:

  1. 程序启动后,第 0 颗灯先单独亮一下红色(代码里先设置单灯颜色并发送)。
  2. 接着整条灯带在红色 ↔ 蓝色之间平滑渐变循环(每 500 步一个方向)。

💡 想换颜色:修改 main.cRED / BLUE 两个结构体的 r/g/b 值(如 {0x00, 0xff, 0x00} 为绿色);想改灯珠数量:改 ws2812_strip 里的 .led_count;觉得太暗:把 .brightness 调大(0.050.5)。

看到第 0 颗先单独亮红、随后整条灯带红蓝平滑渐变即为成功;如果灯带完全不亮、只有个别灯亮或颜色错乱,说明还没成功,对照文末「常见问题与踩坑提示」排查。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):


本文 API 汇总

bl_sys_init()

初始化系统底层资源(系统时钟、中断等),SDK 工程的 main 函数第一步都要调用。

参数:无

返回值:无

ws2812_init(ws2812_strip)

按灯带结构体初始化 WS2812(驱动自研接口,源码见 components/stage/ws2812/ws2812.c)。内部按 led_count 为每颗灯珠分配内存,并把 pin 配置为数据输出脚。

参数

  • ws2812_stripws2812_strip_t 结构体指针,关键字段:led_count(灯珠数量,本教程 46)、brightness(全局亮度 0.0~1.0,本教程 0.05)、pin(数据引脚,本教程 12

返回值:无(结构体为空或 led_count 为 0 时打印错误日志并返回)

ws2812_set_pixel_color(index, r, g, b)

设置指定位置单颗灯珠的颜色(驱动自研接口,源码见 components/stage/ws2812/ws2812.c)。颜色数据内部按 WS2812 要求的 GRB 顺序编排,本 API 只需按常规 RGB 传参。

参数

  • index:灯珠位置,可选值:0~(led_count-1),本教程第 0 颗
  • r:红色分量,0x00~0xFF,本教程 0xff
  • g:绿色分量,0x00~0xFF
  • b:蓝色分量,0x00~0xFF

返回值:无

ws2812_set_pixel_brightness(index, brightness)

单独调整指定灯珠的亮度(驱动自研接口,源码见 components/stage/ws2812/ws2812.c)。

参数

  • index:灯珠位置,可选值:0~(led_count-1)
  • brightness:亮度系数,可选值:0.0~1.0,本教程第 0 颗设为 0.5

返回值:无

ws2812_show_leds()

把内存中排好的全部颜色数据通过数据脚真正发送给灯珠(驱动自研接口,源码见 components/stage/ws2812/ws2812.c)。设置颜色后必须调用它,灯才会变色。

参数:无

返回值:无

smoothcolorTransition(start, end, steps, updateCallback, userData)

从起始颜色平滑渐变到结束颜色(自研接口,源码见 components/stage/ws2812/color_mode.c)。内部把 steps 步插值计算,每一步调用一次回调刷新灯珠。

参数

  • start:起始颜色,color_t 结构体 {r, g, b},本教程 RED
  • end:结束颜色,本教程 BLUE
  • steps:渐变步数,可选值:正整数,本教程 500(越大渐变越慢越细腻)
  • updateCallback:渐变回调函数,形如 void cb(color_t color, void *arg),每步都会被调用
  • userData:传给回调的用户数据,无需要传 NULL

返回值:无

dns_init()

初始化 lwIP 协议栈的 DNS 域名解析模块(网络相关)。本示例没有联网功能,保留官方调用即可,不要删。

参数:无

返回值:无

vTaskDelay(ms)

让当前任务挂起指定毫秒数,期间让出 CPU 给其他任务(FreeRTOS 系统 API,本教程用 pdMS_TO_TICKS 把毫秒换算成系统节拍)。

参数

  • ms:延时毫秒数,可选值:任意非负整数(本教程渐变回调里延时 5ms 控制渐变速度)

返回值:无


完整代码

以下为 main/main.c 完整源码,与官方示例(applications/iot-solution/demo_ws2812/main/main.c)完全一致:

📜 点击展开 main/main.c 完整代码
c
/**
 * @file main.c
 * @author Seahi-Mo (seahi-mo@foxmail.com)
 * @brief
 * @version 0.1
 * @date 2025-07-23
 *
 * @copyright Ai-Thinker co.,ltd (c) 2025
 *
 */

#include <bl_ir.h>
#include <stdio.h>
#include <FreeRTOS.h>
#include <task.h>
#include <bl_irq.h>
#include <bl_sys.h>

#include "ws2812.h"
#include "color_mode.h"
#include "blog.h"
color_t RED = {0xff, 0x00, 0x00};
color_t GREEN = {0x00, 0xff, 0x00};
color_t BLUE = {0x00, 0x00, 0xff};
/**
 * @brief 定义WS2812灯带 只能使用 GPIO11 并且外部需要提供 1K~2K 的上拉电阻
 *
 */
static ws2812_strip_t ws2812_strip = {
    .led_count = 46,
    .brightness = 0.05,
    .pin = 12,
};

static void smoothcolorTransition_callbark(color_t color, void *arg)
{
    ws2812_set_all_pixels_color(color.r, color.g, color.b, ws2812_strip.brightness);
    vTaskDelay(pdMS_TO_TICKS(5));
}

void main(void)
{
    bl_sys_init(); // 初始化系统
    ws2812_init(&ws2812_strip);
    ws2812_set_pixel_color(0, 0xff, 0x00, 0x00);
    ws2812_set_pixel_brightness(0, 0.5);
    ws2812_show_leds();
    dns_init();
    // blog_info("ws2812 demo start");
    // ws2812_set_all_pixels_color(0xFF, 0x00, 0x00, 0.5);
    // vTaskDelay(pdMS_TO_TICKS(1000));
    // ws2812_set_all_pixels_color(0x00, 0xFF, 0x00, 0.5);
    // vTaskDelay(pdMS_TO_TICKS(1000));
    // ws2812_set_all_pixels_color(0x00, 0x00, 0xFF, 0.5);
    vTaskDelay(pdMS_TO_TICKS(1000));

    while (1)
    {
        // 颜色渐变模式
        smoothcolorTransition(RED, BLUE, 500, smoothcolorTransition_callbark, NULL);
        smoothcolorTransition(BLUE, RED, 500, smoothcolorTransition_callbark, NULL);
    }
}

常见问题与踩坑提示

⚠️ 灯带完全不亮
原因:数据脚接错(DIN 没接 IO12)、DO/DI 方向接反、未共地或供电不足
解决:确认 DIN 接 IO12(DO 是输出端,接错收不到数据);VCC 接 5V 且与开发板 GND 共地;灯带较长时单独用电源供电并共地

⚠️ 颜色不对(红灯发绿/发蓝等)
原因:WS2812 按 GRB(绿红蓝)顺序收数据,传参顺序错了颜色就串位
解决:本驱动库内部已按 GRB 编排,直接按 r/g/b 传参即可;若你自行写驱动,注意先发绿色分量

⚠️ 灯太暗、几乎看不见
原因:官方示例全局亮度只有 0.05(5%)
解决:把 ws2812_strip 里的 .brightness 调大到 0.5,或用 ws2812_set_pixel_brightness 单独提亮

⚠️ 只有第一颗灯亮、后面的灯不亮
原因:级联断了——第一颗的 DO 没有接到第二颗的 DI
解决:检查灯带级联接线;也可把 led_count 改成实际颗数(如 8)验证单颗接线是否正常

⚠️ 想用 IR 方式驱动
原因:IR 方式对引脚有特殊要求
解决:官方说明 IR 驱动仅支持 GPIO11 且需要一个 1K~2K 上拉电阻;修改 proj_config.mkCONFIG_WS2812_MODE:=IR_MODE 后重新编译烧录

⚠️ 烧录时提示无法打开串口 / 一直卡住等待
原因:串口设备号不对、权限不足,或未进入下载模式、数据线只能充电
解决:确认设备号 ls /dev/ttyUSB*,权限不足执行 sudo usermod -aG dialout $USER;烧录时按提示长按 EN 键,换一根能传数据的 Type-C 数据线后重试

运行自检

第 0 颗灯先单独亮红一下,随后整条 46 颗灯带在红色 ↔ 蓝色之间平滑渐变循环,即 WS2812 灯带驱动验证通过。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23