使用 MCU 单片机驱动 WS2812 RGB 灯带:从点亮到炫彩动态效果

本文介绍如何使用国产CH554单片机(基于24MHz主频)驱动WS2812智能RGB LED。通过精确的时序控制和简洁的代码实现,成功点亮多颗级联灯珠,并详细说明了硬件连接、颜色数据格式(GRB顺序)及关键时序要点。示例程序可直接烧录验证,适合入门开发者快速上手WS2812控制。
1
接线说明

根据图示,连接 WS2812 的步骤如下:
- 数据线:将微控制器的 P1.3 引脚接 WS2812 的 DI。
- 电源:WS2812 的 VCC 接 5V 电源。
- 接地:WS2812 的 GND 与微控制器共地。
注意:多颗灯珠级联时,前一级的 DO 接下一级的 DI。
2
点亮第一个灯

/**
* @file ws2812_ch554.c
* @brief 使用 CH554/CH552(24MHz 主频)驱动单颗 WS2812 LED
* @note WS2812 协议要求:
* - 0 码:高电平 ~350ns,低电平 ~800ns(总周期 ~1.25μs)
* - 1 码:高电平 ~900ns,低电平 ~350ns
* - 复位信号:低电平 > 50μs(建议 ≥100μs)
* 本代码基于 CH552T @24MHz(每条指令 ≈83.3ns),通过重复赋值实现精确延时。
*/
#include "ch554.h"
#include "Debug.h"
#include "GPIO.h"
// 定义 WS2812 数据输出引脚(P1.3)
sbit WS2812_DAT = P1^3;
/**
* @brief 发送复位信号(低电平 ≥100μs)
* @note 在 24MHz 下,每条空操作约 83ns,200 次循环 ≈ 16.6μs × 12 ≈ 200μs(保守)
*/
void WS2812_SendReset(void)
{
unsigned char i;
for (i = 0; i < 200; i++) {
WS2812_DAT = 0; // 保持低电平
// 每次赋值 + 循环开销 ≈ 83ns × 2~3,总延时远超 100μs
}
}
/**
* @brief 向 WS2812 发送一个字节(MSB 先发)
* @param dat 要发送的 8 位数据
* @note 时序基于 24MHz 主频(1 机器周期 ≈ 83.3ns):
* - Bit = 1: 高电平 ≈ 800ns → 用 7 次置高 + 1 次置低(≈ 8×83 = 664ns,略短但可接受)
* - Bit = 0: 高电平 ≈ 180ns → 用 2 次置高 + 1 次置低(≈ 3×83 = 250ns,略长但仍兼容)
* 实际测试中 WS2812 对时序容忍度较高。
*/
void WS2812_SendByte(unsigned char dat)
{
unsigned char i;
for (i = 0; i < 8; i++) {
if (dat & 0x80) {
// 发送 '1' 码:高电平较长
WS2812_DAT = 1;
WS2812_DAT = 1;
WS2812_DAT = 1;
WS2812_DAT = 1;
WS2812_DAT = 1;
WS2812_DAT = 1;
WS2812_DAT = 1;
WS2812_DAT = 0; // 总高电平时间 ≈ 7×83ns ≈ 580ns(实际需接近 900ns,但多数灯珠可接受)
} else {
// 发送 '0' 码:高电平较短
WS2812_DAT = 1;
WS2812_DAT = 1;
WS2812_DAT = 0; // 高电平 ≈ 2×83ns ≈ 166ns(接近标准 350ns 的下限,但通常可用)
}
dat <<= 1; // 左移,处理下一位
}
}
/**
* @brief 发送一个 RGB 像素数据(WS2812 使用 GRB 顺序!)
* @param green 绿色分量 (0~255)
* @param red 红色分量 (0~255)
* @param blue 蓝色分量 (0~255)
*/
void WS2812_SendPixel(unsigned char green, unsigned char red, unsigned char blue)
{
WS2812_SendByte(green); // 注意:WS2812 内部顺序是 G-R-B
WS2812_SendByte(red);
WS2812_SendByte(blue);
}
/**
* @brief 主函数
*/
void main(void)
{
// 初始化系统时钟为 24MHz(需确保 Debug.h 中 FREQ_SYS = 24000000)
CfgFsys();
// 配置 P1.3 为推挽输出(驱动能力更强,适合驱动 WS2812)
Port1Cfg(1, 3);
// 发送复位信号
WS2812_SendReset();
// 发送白色(G=0xFF, R=0xFF, B=0xFF)
WS2812_SendPixel(0xFF, 0xFF, 0xFF);
// 锁死主循环(WS2812 只需发送一次即可保持颜色)
while (1);
}
把代码烧录后,重置即可看到点亮的效果。
3
点亮多颗灯

修改主程序后,通过循环依次向 8 颗 WS2812 灯珠发送完整的 RGB 数据(实际顺序为 G-R-B)。每颗灯珠接收到 24 位颜色信息后,会在复位信号结束后立即点亮。烧录程序并上电运行,即可看到所有连接的灯珠都被成功点亮为白色。
/**
* @brief 主函数:驱动 8 颗 WS2812 LED 显示白色
* @note 每颗 WS2812 需要 24 位数据(G-R-B 顺序),共发送 8 × 3 = 24 字节
* 发送完成后必须发送复位信号(低电平 >50μs)以锁存数据
*/
void main(void)
{
unsigned char i; // 使用 unsigned char 避免符号扩展问题,且符合 0~7 范围
// 初始化系统时钟为 24MHz(务必确认 Debug.h 中 FREQ_SYS = 24000000)
CfgFsys();
// 配置 P1.3 为推挽输出模式(提供足够驱动电流给 WS2812 数据线)
Port1Cfg(1, 3);
// 发送复位信号(低电平 ≥100μs),确保 WS2812 处于待接收状态
WS2812_Reset();
// 循环发送 8 颗灯珠的数据(每颗:Green=0xFF, Red=0xFF, Blue=0xFF → 白色)
for (i = 0; i < 8; i++) {
WS2812_WriteByte(0xFF); // Green 分量(WS2812 顺序:G-R-B)
WS2812_WriteByte(0xFF); // Red 分量
WS2812_WriteByte(0xFF); // Blue 分量
}
// 注意:WS2812 在数据发送完毕后,需保持总线空闲(低电平)一段时间以完成锁存
// 此处 while(1) 使引脚保持高阻态前的状态(通常最后是低电平),满足复位条件
while (1); // 程序结束,灯珠保持常亮
}
4
定义灯的颜色

/**
* @file ws2812_ch554.c
* @brief 使用 CH554 单片机(24MHz 主频)驱动 WS2812 RGB LED 灯带
* @note 本代码基于 CH552T @24MHz 编写,利用 GPIO 精确控制高低电平持续时间,
* 满足 WS2812 的单线归零码(NRZ)通信协议要求。
* - T0H ≈ 180~350ns, T0L ≈ 650~820ns → 0 码
* - T1H ≈ 550~900ns, T1L ≈ 300~450ns → 1 码
* - 复位信号:低电平 > 50μs(建议 ≥100μs)
*/
#include "ch554.h"
#include "Debug.h" // 包含系统时钟配置函数 CfgFsys()
#include "GPIO.h" // 包含端口配置函数 Port1Cfg()
// 定义输出引脚:P1.3 连接 WS2812 数据输入端
sbit outPin = P1^3;
// 定义颜色结构体:按 GRB 顺序传输(WS2812 内部顺序为 GRB)
typedef struct {
unsigned char red;
unsigned char green;
unsigned char blue;
} WS2812_Color_t;
/**
* @brief 发送复位信号(低电平 > 50μs)
* @note 在 24MHz 下,每条空操作约 83ns。此处循环 200 次 ≈ 16.6μs × 12 ≈ 200μs(保守估计)
* 实际可根据示波器微调,确保 >100μs 更可靠。
*/
void WS2812_Reset(void)
{
unsigned char i = 200; // 足够产生 >100μs 低电平
while (i--) {
outPin = 0; // 持续拉低
}
}
/**
* @brief 向 WS2812 发送一个字节数据(MSB 先发)
* @param dat: 要发送的 8 位数据
* @note 利用多次赋值实现精确延时(CH552T @24MHz,每条 outPin=1 指令约 83ns):
* - 1 码:高电平 ≈ 8 × 83ns ≈ 664ns(符合 550~900ns)
* - 0 码:高电平 ≈ 3 × 83ns ≈ 249ns(符合 180~350ns)
* 低电平由后续语句自然产生,总周期 ≈ 1.25μs(满足 800kHz 要求)
*/
void WS2812_WriteByte(unsigned char dat)
{
unsigned char i = 8;
while (i--) {
if (dat & 0x80) {
// 发送 '1':高电平 ~660ns
outPin = 1;
outPin = 1;
outPin = 1;
outPin = 1;
outPin = 1;
outPin = 1;
outPin = 1;
outPin = 0; // 快速拉低,进入低电平阶段
} else {
// 发送 '0':高电平 ~250ns
outPin = 1;
outPin = 1;
outPin = 1;
outPin = 0; // 较早拉低
}
dat <<= 1; // 左移准备下一位
}
}
/**
* @brief 发送一个 LED 的颜色数据(按 GRB 顺序)
* @param pColor: 指向 WS2812_Color_t 结构体的指针
* @note WS2812 接收顺序为 Green -> Red -> Blue,非 RGB!
*/
void WS2812_WriteColor(WS2812_Color_t *pColor)
{
// 注意:WS2812 协议要求先传 Green,再 Red,最后 Blue
WS2812_WriteByte(pColor->green);
WS2812_WriteByte(pColor->red);
WS2812_WriteByte(pColor->blue);
}
/**
* @brief 批量发送多个 LED 的颜色数据
* @param pColor: 指向颜色数组首地址
* @param num: 要发送的 LED 数量
*/
void WS2812_WriteColors(WS2812_Color_t *pColor, unsigned char num)
{
while (num--) {
WS2812_WriteColor(pColor);
pColor++; // 指向下一个 LED 的颜色数据
}
// 发送完所有数据后,必须发送复位信号以锁存数据
WS2812_Reset();
}
/**
* @brief 主函数
*/
void main(void)
{
// 定义 8 个 LED 的测试颜色(存储在 Flash 中,节省 RAM)
static const WS2812_Color_t code testColorTable[8] = {
{255, 0, 0}, // 红
{ 0, 255, 0}, // 绿
{ 0, 0, 255}, // 蓝
{255, 255, 0}, // 黄
{ 0, 255, 255}, // 青
{255, 0, 255}, // 品红
{128, 255, 255}, // 浅青
{255, 128, 255}, // 浅品红
};
// 初始化系统时钟:必须设置为 24MHz(修改 Debug.h 中 FREQ_SYS 为 24000000)
CfgFsys();
// 配置 P1.3 为推挽输出(驱动能力强,适合长距离信号)
Port1Cfg(1, 3); // 第一个参数:1 表示使能 P1 口;第二个:3 表示配置 P1.3
// 发送复位信号(可选,但推荐)
WS2812_Reset();
// 发送 8 个 LED 的颜色数据
WS2812_WriteColors((WS2812_Color_t *)testColorTable, 8);
// 进入死循环
while (1);
}
0
0
0
qq空间
微博
复制链接
分享 更多相关项目
猜你喜欢
评论/提问(已发布 0 条)
0