ws2812.h
#ifndef __WS2812_H__
#define __WS2812_H__#include "config.h"
#include "GPIO.h"
#include "delay.h"#define WS2812_H {P35=1;}
#define WS2812_L {P35=0;}#define PIXEL_NUM 22void WS2812_LOW(void);
void WS2812_High(void);
void WS2812_WriteByte(unsigned char red, unsigned char green, unsigned char blue);
void WS2812_SetColor(unsigned char red, unsigned char green, unsigned char blue, unsigned char num);
void WS2812_CloseAll(void);
void WS2812_RGB_Waterflow(uint8_t red, uint8_t green, uint8_t blue, uint8_t delaytime);
#endif
ws2812.c
//#include <STC8G.H>
//#include <intrins.h>#include "ws2812.h"void WS2812_High(void)
{WS2812_H;_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();WS2812_L;_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
}void WS2812_LOW(void)
{WS2812_H;_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();WS2812_L;_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
}/*** @brief 设置单个灯珠颜色* @param red 红色的亮度 范围0~255* @param green 绿色的亮度 范围0~255* @param blue 蓝色的亮度 范围0~255* @retval 无*/void WS2812_WriteByte(uint8_t red, uint8_t green, uint8_t blue)
{uint8_t i, j;uint8_t color[3]; // WS2812 的顺序是 GRBcolor[0] = green;color[1] = red;color[2] = blue;for (j = 0; j < 3; j++) {for (i = 0; i < 8; i++) {if (color[j] & 0x80) {WS2812_High();} else {WS2812_LOW();}color[j] <<= 1;}}
}/*** @brief 设置多个灯珠颜色* @param red 红色的亮度 范围0~255* @param green 绿色的亮度 范围0~255* @param blue 蓝色的亮度 范围0~255* @param num 灯珠数量 范围0~255* @retval 无*/
级联灯珠亮
//void WS2812_SetColor(unsigned char red, unsigned char green, unsigned char blue, unsigned char num)
//{
// unsigned char i;
// for (i = 1; i <= num; i++) {
// WS2812_WriteByte(red, green, blue);
// }
//}// 测试级联灯珠收尾亮,中间灯珠灭
void WS2812_SetColor(unsigned char red, unsigned char green, unsigned char blue, unsigned char num)
{unsigned char i;for (i = 1; i <= num; i++) {if (i == 6 || i == 7 || i == 8 || i == 9 || i == 10 || i == 11) {WS2812_WriteByte(0, 0, 0);} else {WS2812_WriteByte(red, green, blue);}} 编号为偶数的灯珠熄灭// for (i = 1; i <= num; i++) {// if (i%2) {// WS2812_WriteByte(0, 0, 0);// } else {// WS2812_WriteByte(red, green, blue);// }// } 全亮// for (i = 1; i <= num; i++) {// WS2812_WriteByte(red, green, blue);// }
}/*** @brief 清除多个灯珠颜色* @param* @retval 无*/
void WS2812_CloseAll(void)
{uint8_t i;for (i = 0; i < PIXEL_NUM; ++i) {WS2812_WriteByte(0, 0, 0);}
}/**
* @brief RGB流水灯,流水顺序:G-灭-R-灭-B-灭* @param red 红色的亮度 范围0~255* @param green* @param blue* @param delaytime* @retval 无*/void WS2812_RGB_Waterflow(uint8_t red, uint8_t green, uint8_t blue, uint8_t delaytime)
{uint8_t i;EA = 0;// WS2812_CloseAll();for (i = 1; i <= PIXEL_NUM; ++i) {WS2812_SetColor(0, green, 0, i);delay_ms(delaytime);}// WS2812_CloseAll();for (i = 1; i <= PIXEL_NUM; ++i) {WS2812_SetColor(red, 0, 0, i);delay_ms(delaytime);}// WS2812_CloseAll();for (i = 1; i <= PIXEL_NUM; ++i) {WS2812_SetColor(0, 0, blue, i);delay_ms(delaytime);}// WS2812_CloseAll();EA = 1;
}
main.c
#include "config.h"
#include "timer.h"
#include "GPIO.h"
#include "delay.h"
#include "STC8XXXX.H"
#include "ws2812.h"uint8_t i = 0;//========================================================================
// 函数: void delay_ms(unsigned char ms)
// 描述: 主函数
// 参数: none.
// 返回: none.
// 版本:
// 日期:
// 备注: 外部信号干扰时序
//========================================================================
void main(void)
{GPIO_config();Timer_config();EA = 1; // 定时器中断会对WS2812时序有影响LED_WaterFlow_OFF();while (1) {Single_Scan();Single_Control();WS2812_CloseAll();// // GRB 全亮
// EA = 0;
// WS2812_SetColor(0, 0x1e, 0, PIXEL_NUM);
// EA = 1;
// delay_ms(255);
// EA = 0;
// WS2812_SetColor(0x1e,0, 0, PIXEL_NUM);
// EA = 1;
// delay_ms(-255);
// EA = 0;
// WS2812_SetColor(0, 0, 0x1e, PIXEL_NUM);
// WS2812_CloseAll();
// EA = 1;
// delay_ms(255);WS2812_RGB_Waterflow(20,20,20,25);}
}
从代码中可以看出,您正在使用 STC8H 系列单片机通过 IO 口直接驱动 WS2812 全彩 LED 灯带,并且自定义了时序函数 WS2812_High
和 WS2812_LOW
来手动控制 WS2812 的时序。根据 24MHz 的时钟频率以及 _nop_()
的使用,这样的控制方法在较小规模的灯珠数量下应该是可以工作的。
改进与优化建议
-
时序的精确性:
WS2812 需要非常严格的时序(例如高电平和低电平持续时间),当前的做法通过_nop_()
来延时。如果需要更高精度,可以通过定时器来精确控制时序,而不是仅仅依赖_nop_()
。你可以使用定时器来生成精确的脉冲宽度,比如设置定时器在 24MHz 的系统时钟下产生 1.25μs 或者 0.4μs 的延时,从而精确控制高电平和低电平的持续时间。
-
外部信号干扰时序:
在发送 WS2812 数据的过程中,外部中断或其他任务可能打乱时序,导致灯珠颜色显示异常。可以考虑在发送灯珠数据期间禁用所有中断,确保时序的完整性。EA = 0; // 关闭全局中断 WS2812_SetColor(red, green, blue, num); // 发送数据 EA = 1; // 开启全局中断
-
简化延时处理:
当前的WS2812_High()
和WS2812_LOW()
函数中使用了大量的_nop_()
延时,这会使得代码看起来不够简洁且不易调节。可以将延时函数封装成一个独立函数,通过参数控制延时时间:void DelayNops(uint8_t n) {while (n--) {_nop_();} }void WS2812_High(void) {WS2812_H;DelayNops(18); // 根据时钟频率调节这个数值WS2812_L;DelayNops(14); // 调节低电平延时 }void WS2812_LOW(void) {WS2812_H;DelayNops(7); // 高电平短时间WS2812_L;DelayNops(18); // 低电平长时间 }
这样不仅代码更加清晰,还可以灵活调整延时时间,以适应不同的时钟频率或不同的时序需求。
-
代码效率优化:
可以将WS2812_WriteByte()
中的三个 for 循环合并成一个循环,依次发送 RGB 数据,减少不必要的重复代码:void WS2812_WriteByte(uint8_t red, uint8_t green, uint8_t blue) {uint8_t i;uint8_t color[3] = {green, red, blue}; // WS2812 的顺序是 GRBfor (int j = 0; j < 3; j++) {for (i = 0; i < 8; i++) {if (color[j] & 0x80) {WS2812_High();} else {WS2812_LOW();}color[j] <<= 1;}} }
-
级联灯珠控制逻辑:
您已经实现了对不同灯珠颜色的控制逻辑,特别是设定某些灯珠关闭,其它灯珠点亮。如果需要更复杂的动画效果,可以使用数组来存储每个灯珠的 RGB 值,并根据外部信号或定时器事件来更新这些颜色。uint8_t led_colors[NUM_LEDS][3]; // 定义一个数组存储每个灯珠的RGB颜色void update_led_colors(uint8_t red, uint8_t green, uint8_t blue) {for (int i = 0; i < NUM_LEDS; i++) {// 更新颜色逻辑,可以根据需要修改led_colors[i][0] = green;led_colors[i][1] = red;led_colors[i][2] = blue;} }void WS2812_SetAll(void) {for (int i = 0; i < NUM_LEDS; i++) {WS2812_WriteByte(led_colors[i][1], led_colors[i][0], led_colors[i][2]);} }
总结
通过使用定时器、优化中断控制、合并 RGB 循环、封装延时函数等方法,你可以在 STC8H 单片机中更精确和高效地驱动 WS2812 灯带,同时避免外部信号干扰时序问题。
总结
- WS2812灯珠时序要求非常严格,程序中设置了中断会导致灯珠有闪烁,需要在发送灯珠时序的地方关闭中断,时序发送完毕后打开中断;
- WS2812 24bit 数据结构,高位先发,按照 GRB 的顺序发送数据(G7→G6→………B0),如果按照RGB的顺序发送,在发送R的数据的时候,第一颗灯珠会闪烁或者颜色叠加绿色分量,导致颜色偏黄