圣诞节期间,为了更好的渲染节日的气氛,彩灯是必不可少的装饰品。然而普通的彩灯使用一年就可能产生质量问题并且只能使其亮或是不亮,如果有一款彩灯可以每年重复利用并且可以按照自己的想法来改变灯的闪烁方式,那么圣诞装饰就能够变得方便、环保且新颖有趣。
本项目的目标旨在根据已有的雪花灯板和马老师已完成的c语言的程序编写和封装,通过编程驱动使得LED雪花灯板按照经过独特设计的方式来闪烁从而使得圣诞彩灯富有变化。
在硬件方面,使用了搭载了STC15W24单片机、37个Ws2812的LED灯的硬禾学堂出品的雪花灯板。
在软件方面,使用Keil uvision4作为编译软件,stc.isp作为单片机驱动软件,c语言作为编程语言。
为了让雪花板上的一个LED灯亮,在编程中需要通过串口通信发送24位数据给LED灯。如果想要所有的LED灯一起亮,则需要发送24*37=888位数据。这些ws2812LED灯会按照顺序依次读取属于自己的24Bit的数据并且当再次接收到一组数据时会清除之前的24位数据。
数据传输流程如下图所示:
以顺序点亮这一封装函数为例:
void Water_lamp(unsigned char *p)//按1-37顺序点亮
{
unsigned char count;
unsigned char count_sum;
for(count_sum=0;count_sum<37;count_sum++)
{
for(count=0;count<=count_sum;count++)
{
SendOnePix(p);
}
ResetDataFlow();
DelayMs(100);
}
ResetDataFlow();
}
当想让前n个LED灯被点亮时,需要有24*n位的数据传入,且多亮一个灯时前面n个灯的数据要重新发送。
在使用顺序亮灯进行了一些设计后,我希望能根据顺序亮灯写出倒序亮灯的程序。在多次尝试在顺序函数中的简单改变无果后,我重新分析了亮灯的原理,也就是上文所说的内容。这时,我才注意到如果想要倒序亮灯,则需要向排序在该灯之前的所有灯都发送24位0的数据才能实现。
具体程序展示:
void Water_lamp_reverse(unsigned char *p)//按37-1顺序点亮
{
unsigned char count;
unsigned char count_sum;
for(count_sum=36;count_sum>0;count_sum--)
{
for(count=0; count<37;count++)
{
if(count<count_sum )
{
SendOnePix(close);
}
else
{
SendOnePix(p);
}
}
ResetDataFlow();
DelayMs(100);
}
ResetDataFlow();
}
参考设计程序介绍
Ws2812.c
函数 | 功能 |
void ResetDataFlow(void) | 复位,为下一次发送做准备 |
void SendOnePix(unsigned char *ptr) | 发送一个像素点的24bit数据 |
Delay.c
函数 | 功能 |
void DelayMs(unsigned char m) | 延时函数 |
void Delay100Ms(unsigned char m) | 延时100ms子函数(晶振频率33MHz时) |
Main.c
函数 | 功能 |
void Flash_WS2812(unsigned char *p) | 整体闪烁,传递颜色参数 |
void LED_Water(unsigned char *p) | 由内而外流水点亮 |
void LED_Water_Color(unsigned char *p | 由内而外流水点亮,颜色变化 |
void LED_Single_Flower(void) | 单圈单个灯绽放点亮 |
void Water_lamp(unsigned char *p) | 按1-37顺序点亮 |
void DIY_SNOW(unsigned char *p) | 由外向内,按照数组的颜色顺序显示,最多五个颜色 |
void Flower(void) | 一圈一圈点亮 |
void Flower_In(void) | 一圈圈逐步点亮(外内) |
void Gradual_change(unsigned char *p) | 渐变亮 |
void Breathing_WS2812(unsigned char level,unsigned char Color) | 呼吸渐变,两个参数,第一个表示集中小颜色,第二个表示颜色 |
void WS2812_Close(void) | 将37个灯全部熄灭 |
void WS2812_1_5Line(unsigned char *p,unsigned char line) | 让其整圈一起点亮,传递两个参数,第一个参数为显示颜色,第二个参数是让第几圈点亮,圈数1-5 |
void WS2812_1_5Line_In(unsigned char *p,unsigned char line) | 让其整圈一起点亮,内圈颜色保持不变,传递两个参数,第一个参数为显示颜色,第二个参数是让第几圈点亮,圈数1-5 |
void WS2812_1_5Line_Single(unsigned char *p,unsigned char line) | 让对应圈的灯流水点亮,传递两个参数,第一个参数为显示颜色,第二个参数是让第几圈点亮,圈数1-5 |
void WS2812_LineIn_Single(unsigned char *p,unsigned char line) | 对应圈数最外层流水,里面全部点亮。传递两个参数,第一个参数为显示颜色,第二个参数是让第几圈点亮,圈数2-5 |
来源:马老师完成的亮灯程序封装(https://www.eetree.cn/project/detail/13)
新增函数:
void Water_lamp_reverse(unsigned char *p) 按37-1顺序点亮
主要程序如下:
void main()
{
unsigned char i;
DelayMs(1000); //上电延时
UartInit();
UART1_SendString("STC15W204S\r\nUart is ok !\r\n");//发送字符串检测是否初始化成功
DelayMs(1000);
ResetDataFlow();
WS2812_Close();
//主循环
while(1)
{
Water_lamp(white);
DelayMs(1000);
WS2812_Close();
Water_lamp_reverse(white);
DelayMs(1000);
WS2812_Close();
WS2812_1_5Line_Single(white,1);
WS2812_1_5Line_Single(green,2);
WS2812_1_5Line_Single(red,3);
WS2812_1_5Line_Single(blue,4);
WS2812_1_5Line_Single(org,5);
DelayMs(1000);
WS2812_Close();
LED_Water_Color(color);
WS2812_Close();
for(i=1;i<6;i++){
WS2812_1_5Line(white,i);
DelayMs(1000);
WS2812_Close();
WS2812_LineIn_Single(green,i);
DelayMs(1000);
WS2812_Close();
WS2812_LineIn_Single(red,i);
DelayMs(1000);
WS2812_Close();
WS2812_LineIn_Single(blue,i);
DelayMs(1000);
WS2812_Close();
WS2812_LineIn_Single(org,i);
DelayMs(1000);
WS2812_Close();
DelayMs(1000);
WS2812_Close();
}
WS2812_1_5Line_Single(white,1);
WS2812_1_5Line_Single(green,2);
WS2812_1_5Line_Single(red,3);
WS2812_1_5Line_Single(blue,4);
WS2812_1_5Line_Single(org,5);
DelayMs(2000);
WS2812_Close(); }
}
亮灯流程如流程图中所示:
另外,发现当编程文件在11.7KB或以上时,文件过大,单片机驱动软件stp.isp发出警告。
查询百度后得知,文件所占ROM大小为:data=106.0 + xdata=64 + code=3890 大约为4K,因为该程序接近能执行的最大文件大小,于是可知该单片机ROM为4K,当超过4K时则无法正常工作。但对于正常的亮灯设计来说,4K的大小也是绰绰有余了。