简介
本文将带你了解如何用 STC 8H8K64U 单片机控制 WS2812 全彩 LED 灯珠。我们会先简单介绍 WS2812 的原理,再详细分析如何通过程序实现点亮效果。
WS2812 简单介绍
WS2812 是一种非常流行的全彩 RGB LED,它最大的特点是内置控制芯片,通过一根数据线就可以实现对灯珠颜色和亮度的精准控制。以下是它的一些关键特性:
单线通信:通过一个数据线传输控制信号,级联多个灯珠时,后续灯珠从前一个灯珠的输出端获取数据。
24 位颜色控制:每颗灯珠由红、绿、蓝三个颜色通道组成,每个通道占 8 位(0~255)。
级联控制:支持多个灯珠串联,单片机只需要控制第一颗灯珠,后续灯珠会自动接收剩余数据。

实现步骤
首先,你需要以下材料:
一块 STC 8H8K64U 单片机开发板。
一颗或多颗 WS2812 灯珠。
一根连接灯珠和单片机的数据线,以及电源(一般 5V)。
硬件连接示意:
WS2812 DIN 接单片机的 GPIO 口(如 P21)。
WS2812 VCC 接 5V 电源。
WS2812 GND 接地。
WS2812 通信协议
WS2812 使用单线协议,传输的每一位数据用高低电平来表示:
逻辑 1:高电平持续约 0.7 µs,低电平持续约 0.6 µs。
逻辑 0:高电平持续约 0.35 µs,低电平持续约 0.8 µs。
复位信号:低电平保持至少 50 µs,表示一帧数据结束。
24 位数据的顺序为:绿色 8 位 → 红色 8 位 → 蓝色 8 位。
(建议参考不同厂商的时序来确定高低电平的时间, 不同厂商的这个时间略有不同)
如下图所示

下图为典型的通讯时序。

所以即点亮单个LED的话, 需要发送的BIT数为24, 绿色先发, 然后是红色, 然后是蓝色。 每个颜色一共是2^8 即256(0-255),可以通过控制发送0码和1码来自由组合。
程序实现如下
由于采用的STC8H8H64U,它的单片机IO口的翻转速度可以满足上述的传输时序需要。因此我们可以使用逻辑分析仪等来帮助我们辅助分析0码和1码。如下图所示。
下图展示的为0码,如果顺序颠倒一下, 基本上等于1码

代码如下
#include <AI8H.H>
#include <intrins.h>
typedef unsigned char uint8_t; // 定义无符号字符类型为 uint8_t
// 定义 WS2812 LED 的逻辑 "0" 和 "1" 信号代码
#define ZERO_CODE() P21=1; NOP7(); P21=0; NOP14(); // 输出逻辑 "0"
#define ONE_CODE() P21=1; NOP14(); P21=0; NOP12(); // 输出逻辑 "1"
#define LED_NUMBER 2 // LED 的数量
#define BRIGHTNESS 2 // LED 亮度设置
#define MAX_BRIGHTNESS 255 // LED 最大亮度值
uint8_t led_states[3][3]; // 存储每个 LED 的颜色状态 [LED索引][颜色分量]
// 延时 55 微秒函数
void Delay55us(void)
{
unsigned char i, j;
_nop_(); // 插入一个空指令
i = 2;
j = 180;
do {
while (--j); // 内层延时
} while (--i); // 外层延时
}
// 延时 1000 毫秒函数
void Delay1000ms(void)
{
unsigned char i, j, k;
_nop_(); _nop_(); // 插入两个空指令
i = 122;
j = 193;
k = 128;
do {
do {
while (--k); // 最内层延时
} while (--j); // 中层延时
} while (--i); // 最外层延时
}
// 重置所有 LED 为关
void reset_all()
{
uint8_t index;
for (index = 0; index < 24 * LED_NUMBER; index++) {
ZERO_CODE(); // 发送 0 信号
}
Delay55us(); // 延时 55 微秒,确保数据传输完成
}
// 设置指定 LED 的颜色
void set_led_color(uint8_t led_index, uint8_t green, uint8_t red, uint8_t blue)
{
uint8_t i, mask, number;
// 调整颜色值以应用亮度设置
green = (green * BRIGHTNESS) / MAX_BRIGHTNESS;
red = (red * BRIGHTNESS) / MAX_BRIGHTNESS;
blue = (blue * BRIGHTNESS) / MAX_BRIGHTNESS;
// 保存颜色值到状态数组
led_states[led_index][0] = green;
led_states[led_index][1] = red;
led_states[led_index][2] = blue;
// 遍历每个 LED,输出其颜色数据
for (number = 0; number < LED_NUMBER; number++) {
// 输出绿色分量数据
for (i = 0; i < 8; i++) {
mask = 0x80 >> i; // 获取当前位的掩码
if (led_states[number][0] & mask) {
ONE_CODE(); // 输出逻辑 "1"
} else {
ZERO_CODE(); // 输出逻辑 "0"
}
}
// 输出红色分量数据
for (i = 0; i < 8; i++) {
mask = 0x80 >> i;
if (led_states[number][1] & mask) {
ONE_CODE();
} else {
ZERO_CODE();
}
}
// 输出蓝色分量数据
for (i = 0; i < 8; i++) {
mask = 0x80 >> i;
if (led_states[number][2] & mask) {
ONE_CODE();
} else {
ZERO_CODE();
}
}
}
Delay55us(); // 延时确保数据传输完成
}
// 主函数
void main()
{
// 设置 P21 引脚为推挽输出模式
P2M0 |= 0x02;
P2M1 &= ~0x02;
reset_all(); // 初始化,关闭所有 LED
set_led_color(0, 0, 255, 0); // 设置第一个 LED 为红色 (R=255, G=0, B=0)
set_led_color(1, 255, 255, 0); // 设置第一个 LED 为红色 (R=255, G=0, B=0)
while (1) {
}
}注意,上述程序可以正确的运行的主频为24M的单片机内。 如果需要移植到你的单片机上的话,(需要重新计算延时时间)只需要控制好0码和1码的生成。
下图为STC-ISP调整的主频截图。

运行效果
将程序烧录到单片机中并运行后:
第一颗灯珠会显示红色;
第二颗灯珠会显示绿色;
如果有更多灯珠,会保持默认关闭状态。

如果需要调整亮度的话, 可以修改BRIGHTNESS的定义.
总结
通过这篇教程,我们学习了 WS2812 的通信原理和具体实现方法。尽管 WS2812 的时序要求较高,但通过简单的逻辑分析和代码实现,我们可以轻松完成对灯珠的控制。你可以进一步尝试添加更多灯珠、动态变化颜色等更复杂的效果。
我要赚赏金
