简介
本文将带你了解如何用 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 的时序要求较高,但通过简单的逻辑分析和代码实现,我们可以轻松完成对灯珠的控制。你可以进一步尝试添加更多灯珠、动态变化颜色等更复杂的效果。