第十五章 赤橙黄绿青蓝紫,谁持彩练当空舞

海螺之音

<p class="ql-block">  我们村里的人大多知道我喜欢鼓捣一些电子元器件。这不,一个发小从一堆工业垃圾推旁经过,看到被人家抛弃的两条很长的LED装饰灯带,感觉很漂亮,也很好奇,于是捡了过来让我研究一下,看看能不能修好,看看这灯带点亮花样效果到底如何。</p><p class="ql-block"> 这个发小与我从小是同学,后来还上过两年电子技术学校的,对于电子元件多少应该是有不少了解,如今拿着LED(发光二极管)灯带过来“讨教”,我觉得是有点过分了。</p><p class="ql-block"> “发光二极管有啥好研究的,这玩意儿老简单了。”我说。</p><p class="ql-block"> “但是这种灯带应该是KTV之类的娱乐场所的装饰灯带,可以变色,还可以有流水花样的,我看没那么简单。你看一下。”他固执己见。</p><p class="ql-block"> “变色LED其实也简单,它是把红、绿、蓝三基色发光PN结整合到一个封装在一个LED内部,引出四个电极,一个公共阴极或者公共阳极,然后是红绿蓝三个LED控制极,根据三基色混色原理点亮不同LED组合就可以有发出不同的光。”我说。</p><p class="ql-block"> “但是LED流水花样控制呢?照你这么说,每只LED四条引脚,这么长一条灯带上百个LED,要实现花样流水和颜色变化岂不是需要数百条控制线?而且实际上每条灯带只有三根线,这也不合理。而且还是两条电源线,只剩一根控制线,是串联还是并联?为什么仅凭一根控制线就可以实现变色和花样流水?”他提出了他的疑问。</p><p class="ql-block"> 这个问题提得好,一下子把我问住了。我也好奇地拿起他带过来的LED灯带仔细观察起来。确实如他所言,这个LED灯带只有一个小小的控制盒,控制盒里引出三根线,其中一红一黑标明是供电端和地端,第三条是一根控制线无疑了。百来颗方形的贴片封装LED密密麻麻排成一行,每一颗都有四个引脚,似乎呈串联的状态,组成整条灯带。我们反复插上电源线试了几下,有时候整条灯带闪了一下便灭了,有时候整条灯带常亮白光,没流水也没变色,有时候干脆啥反应都没有。于是判定控制器坏了。拆开控制器,里面是一组电子开关电源模块和一颗小小的8脚LED控制芯片。用万用表测试之后发现开关电源工作正常,输出电压稳定,再对主控芯片的阻容元件一番排查之后,基本可以判定是是主控芯片出了问题。但是这颗主控芯片的型号在生产前已经被厂家打磨干净,这就比较棘手了。由于之前从没接触过此类LED,也没接触过这种控制芯片,这个东西我算是不会修了,于是我只能作罢,让他带了回去。</p><p class="ql-block"> 发小回去了几天,这个灯带的问题一直萦绕在我的心头,我觉得我应该把它彻底弄清楚。从那个LED的外型和连接形式看,它确实不是普通的LED。我在网上搜了一阵子“全彩LED”、“幻彩LED”、“幻彩流水LED”等关键字,好家伙,一个批不经传的神秘的小家伙渐渐浮出水面。有这种的:</p> <p class="ql-block">  有这种的:</p> <p class="ql-block">  还有这种的:</p> <p class="ql-block">  原来这种幻彩LED是一种内嵌控制IC的LED,果然不简单,而且品种非常多,通过查阅大量资料,发现它们的控制方式大同小异,我决定花几块钱随机购买一种名为“WS2812B”的八位幻彩LED来玩一玩。等待了两天,东西到手,先拍个照留念:正面照就是八个灯加几个去除杂波干扰的贴片小电容,非常简单,反面就有得说了,两头各有四个外接端口,其中一端是GND、IN、VCC、GND,两个GND其实是相通的,都是地线,任接一根就可以,IN端是数据输入端,VCC是5V电压接入端;另一端是GND、OUT、VCC、GND,OUT端是接下一个灯珠的IN端,一级一级地“接力”连接下去。</p> <p class="ql-block">  想要正确点亮这种LED,必须先弄清楚它的数据手册。我在网上搜了许久,终于找到一份比较靠谱的资料:</p> <p class="ql-block">  妈妈,我要哭了,全是英文!但是不要太慌张,继续往下拉,只要找到WS2812的控制时序图就好了,毕竟,控制时序图才是掌握各种芯片驱动的密钥!</p> <p class="ql-block">  看到了,看到了,原来给WS2812发送数据是通过发送“1”和“0”来实现的,如果主控芯片的某个引脚要给WS2812发送“1”码,就让该芯片的电平拉高到5V,然后保持750ns(纳秒)到1.6us(微秒)之间,然后再把该引脚电平拉底至0V并保持220ns到420ns之间;如果要发送“0”码,就把该芯片引脚的电平拉高5V,保持220ns到380ns,然后迅速拉低该引脚电平到0V,保持750ns到1.6us。总之发1和发0都与该引脚高电平保持的时间有关,电平长高短低代表1,短高长低代表0,一个码的发送时间周期控制在1.25微秒左右。综合上述资料,我认为把“1”码的高电平时间定在830ns到850ns左右,低电平时间时间控制在350ns到380ns之间,两者加起来在1.2微秒左右,这个误差范围还是可以接受的。把“0”码的高电平时间控制在350ns到380ns之间,然后把低电平时间控制在830ns到850ns之间。1个字节是由8个比特组成的,比如要给WS2812发送254这个数,就连续发送1111 1110这8个二进制码。</p> <p class="ql-block">  虽然看不懂英文,也可以大概理解WS2812的数据结构,一帧完整的WS2812的控制数据结构是24位,8位控制绿的,8位控制红色的,8位控制蓝色的,记为G、R、B,每个灯珠在接收到24个比特的数据之后如果在50us内再无数据进来就马上显示,如果还是有数据进来就把新接收到的数据传到下一个LED。综上所述,如果要控制10个灯珠,就必须连续发送24×10=240比特的数据,控制数据发送格式是顺序发送GRB,高位先发。</p> <p class="ql-block">  好,大致搞清楚了这些资料,就该研究如何用单片机驱动控制这玩意儿了。以STC89C52RC为例,如果系统频率为11.0592MHZ,则一个机器周期为:1÷111.0592×12≈1.085us(微秒),既在此系统晶振频率下,STC89C52RC每执行一个最基本的指令都需要1.085微秒(即1085ns),而WS2812的0码高电平时间要求精细到330到380ns的精度,这很显然是做不到的。如果继续提高系统频率呢?按照官方资料,STC89C52RC最高工作频率可以提高到40MHZ,在此条件下,一个机器周期可以做到300ns,看起来似乎勉强可以满足要求,但是经典51内核的单片机一个进入函数和跳出函数的操作都需要两个机器周期,所以导致循环控制的时序误差太大,在C语言编程条件下很难驱动WS2812,也许使用汇编语言勉强可以。</p><p class="ql-block"> 为此我将不得不考虑STC其它系列的单片机。经过一番查询,发现其中STC15F104W单片机跟之前那个灯带的控制芯片外形差不多,都是8引脚贴片封装,而且是STC公司的新一代Y5超高速内核的单机器周期单片机,理论上在同等系统频率条件下它的执行速度是STC89系列的12倍,内部集成可编程高精度RC振荡器,工作频率从5MHZ到35MHZ可调,如果将其内部RC振荡器调整为24MHZ,则每个机器周期为:1/24MHZ=0.041us,即41ns(纳秒),这样的执行速度驱动WS2812是可以的。而且它的价格很低,所以买来一试。</p><p class="ql-block"> 这是我在网上买来的STC15F104W最小系统板:</p> <p class="ql-block">  如上图所示,这个系统版只有8个引脚,再看一下厂家提供的电路图:</p> <p class="ql-block">  再看一下大概资料:</p> <p class="ql-block">  好了,对于一个电子爱好者来说,了解这么多资料就足够了。</p> <p class="ql-block">  首先,把刚买来的八位LED板与STC15F104W系统板连接起来,VCC对VCC,GND对GND,把单片机的P3.4引脚与LED板的IN端连接,如下图:</p> <p class="ql-block">  接着结合WS2812的控制时序图写一个简单的程序,点亮第一个幻彩LED,并让它显示红色。</p> <p class="ql-block">/******************包含单STC15F104单片机头文件,以及包含空操作函数头文件,宏定义,位定义******************/</p><p class="ql-block">#include &lt;STC15.H&gt;/*包含STC15F104W单片机的头文件*/</p><p class="ql-block">#include &lt;intrins.h&gt;/*包含这个头文件是为了使用_nop_()函数,让单片机空操作一个机器周期,延时0.041us(即41纳秒)的时间*/</p><p class="ql-block">#define uint unsigned int/*宏定义*/</p><p class="ql-block">#define uchar unsigned char</p><p class="ql-block">sbit led_date=P3^4;/*把MCU的P34引脚定义为控制数据输出端*/</p><p class="ql-block">/*********延时函数,在24MHZ系统时钟频率条件下延时100毫秒**************/</p><p class="ql-block">void Delay100ms(void) </p><p class="ql-block">{</p><p class="ql-block"> unsigned char data i, j, k;</p><p class="ql-block"> _nop_();</p><p class="ql-block"> _nop_();</p><p class="ql-block"> i = 10;</p><p class="ql-block"> j = 31;</p><p class="ql-block"> k = 147;</p><p class="ql-block"> do</p><p class="ql-block"> {</p><p class="ql-block"> do</p><p class="ql-block"> {</p><p class="ql-block"> while (--k);</p><p class="ql-block"> } while (--j);</p><p class="ql-block"> } while (--i);</p><p class="ql-block">}</p><p class="ql-block">/******向WS2812写入一个字节*******/</p><p class="ql-block">void ws2812_write_byte(uchar dat)</p><p class="ql-block">{</p><p class="ql-block"> uchar i; /*声明i变量控制写入循环*/</p><p class="ql-block"> for(i=0;i&lt;8;i++)/*循环8次*/</p><p class="ql-block"> {</p><p class="ql-block"> if((dat&gt;&gt;(7-i))&0x01==0x01)/*如果是“1”码*/</p><p class="ql-block"> {</p><p class="ql-block"> led_date=1;/*把P34电平拉高*/</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();/*保持20个机器周期,总共耗时0.0416ns×20=0.833us,根据WS2812的数据手册,此脉冲宽度符合要求*/</p><p class="ql-block"> led_date=0;/*然后把P34电平拉低*/</p><p class="ql-block"> _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_();_nop_();/*保持9个机器周期,即0.0416×9=0.375us,符合数据手册要求*/</p><p class="ql-block"> }</p><p class="ql-block"> else /*否则如果是“0”码*/</p><p class="ql-block"> {</p><p class="ql-block"> led_date=1;/*<span style="font-size:18px;">把P34电平拉高*/</span></p><p class="ql-block"> _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_();_nop_(); /*保持9个机器周期,即0.375us*/</p><p class="ql-block"> led_date=0;/*然后拉低P34电平*/</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); /*保持20个机器周期,即0.833us*/</p><p class="ql-block"> }</p><p class="ql-block"> }</p><p class="ql-block">}</p><p class="ql-block">/***************向WS2812写入颜色数据,有三绿、红、蓝三个参数,每个参数的取值范围是0~255,每个参数有8个二进制码,三个参数共24比特,构成完整的一帧数据************************/</p><p class="ql-block">void ws2812_write_color(uchar green,uchar red,uchar blue)</p><p class="ql-block">{</p><p class="ql-block"> ws2812_write_byte(green);/*写绿*/</p><p class="ql-block"> ws2812_write_byte(red);/*写红*/</p><p class="ql-block"> ws2812_write_byte(blue);/*写蓝*/</p><p class="ql-block">}</p><p class="ql-block">/**************主程序入口*************/</p><p class="ql-block"> void main()</p><p class="ql-block"> {</p><p class="ql-block"> while(1) /*无限循环*/</p><p class="ql-block"> {</p><p class="ql-block"> ws2812_write_color(0x00,0xff,0x00);</p><p class="ql-block">/*这里的GRB参数,G=0,R=255,B=0,即让LED显示纯红色*/</p><p class="ql-block"> Delay100ms();</p><p class="ql-block">/*延时100毫秒,让LED显示*/</p><p class="ql-block"> }</p><p class="ql-block"> }</p> <p class="ql-block">  好,写完之后点击编译,没有错误,没有警告,打开STC-ISP程序下载器,设置好各种参数,并在IRC时钟一栏选择24MHZ。</p> <p class="ql-block">  然后将生成的hex文件下载到单片机里,可以看到程序运行结果:</p> <p class="ql-block">  真不错,第一个WS2812幻彩灯正确点亮,说明上面的程序是完全正确的。接下来是试着写一个小程序同时控制8个灯。正好想起毛主席的一首《菩萨蛮》:“赤橙黄绿青蓝紫,谁持彩练当空舞?雨后复斜阳,关山阵阵苍。当年鏖战急,弹洞前村碧。装点此关山,今朝更好看。”好吧,就让这8个WS2812静态显示红、橙、黄、绿、青、蓝、紫、最后一个偏棕色的吧。编程思路如下:首先应该是声明一个一维数组,此数组内部存储有24个数据,每三个数据控据一个WS2812,其组成结构是:{G1R1B1G2R2B2G3R3B3G4R4B4G5R5B5G6R6B6G7R7B7G8R8B8},然后再声明一个变量i,控制程序遍历这个一维数组并把它们从头到尾一个一个地全部写入WS2812电路板的IN端中。具体程序如下:</p> <p class="ql-block">/**********以下程序与上个程序大多相同,所以相同部分不再注释*********/</p><p class="ql-block">#include &lt;STC15.H&gt;</p><p class="ql-block">#include &lt;intrins.h&gt;</p><p class="ql-block">#define uint unsigned int</p><p class="ql-block">#define uchar unsigned char</p><p class="ql-block">sbit led_date=P3^4;</p><p class="ql-block"><span style="font-size:18px;">/*****************就是下面这个一维数组,共有24个数据,每三个数据控制一个幻彩LED**************************/</span></p><p class="ql-block">uchar color_array[]={0x00,0xff,0x00,0xff,0x61,0x00,0xff,0xff,0x00,0xff,0x00,0x00,0xff,0x00,0xff,0x00,0x00,0xff,0x00,0xff,0xff,0x55,0x55,0xff};</p><p class="ql-block">void Delay100ms(void) //@24.000MHz</p><p class="ql-block">{</p><p class="ql-block"> unsigned char data i, j, k;</p><p class="ql-block"> _nop_();</p><p class="ql-block"> _nop_();</p><p class="ql-block"> i = 10;</p><p class="ql-block"> j = 31;</p><p class="ql-block"> k = 147;</p><p class="ql-block"> do</p><p class="ql-block"> {</p><p class="ql-block"> do</p><p class="ql-block"> {</p><p class="ql-block"> while (--k);</p><p class="ql-block"> } while (--j);</p><p class="ql-block"> } while (--i);</p><p class="ql-block">}</p><p class="ql-block">void ws2812_write_byte(uchar dat)</p><p class="ql-block">{</p><p class="ql-block"> uchar i;</p><p class="ql-block"> for(i=0;i&lt;8;i++)</p><p class="ql-block"> {</p><p class="ql-block"> if((dat&gt;&gt;(7-i))&0x01==0x01)</p><p class="ql-block"> {</p><p class="ql-block"> led_date=1;</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();</p><p class="ql-block"> led_date=0;</p><p class="ql-block"> _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_();_nop_();</p><p class="ql-block"> }</p><p class="ql-block"> else</p><p class="ql-block"> {</p><p class="ql-block"> led_date=1;</p><p class="ql-block"> _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_();_nop_(); </p><p class="ql-block"> led_date=0;</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); </p><p class="ql-block"> }</p><p class="ql-block"> } </p><p class="ql-block">}</p><p class="ql-block">/******************下面这个函数是最重要的代码,负责向WS2812电路板连续写入24个数据***********************/</p><p class="ql-block">void _8_ws2812_write_color()</p><p class="ql-block">{</p><p class="ql-block"> uchar i; /*声明临时变量i*/</p><p class="ql-block"> for(i=0;i&lt;24;i++)/*变量i从0增到23*/</p><p class="ql-block"> {</p><p class="ql-block"> ws2812_write_byte(color_array[i]);</p><p class="ql-block">/*将一维数组的第i个数据写入WS2812*/</p><p class="ql-block"> }</p><p class="ql-block">}</p><p class="ql-block">/*************主函数*****************/</p><p class="ql-block"> void main()</p><p class="ql-block"> {</p><p class="ql-block"> while (1) /*无限循环*/</p><p class="ql-block"> {</p><p class="ql-block"> _8_ws2812_write_color(); /*写入24个数据*/</p><p class="ql-block"> Delay100ms(); /*延时100毫秒,让LED显示*/</p><p class="ql-block"> } </p><p class="ql-block"> }</p> <p class="ql-block">  好,点击编译,没有错误,也没有警告,将程序下载到单片机里运行,显示效果跟预想的完全一致:(为了拍摄效果,我将手机相机爆光率拉低,否则LED太亮了,根本观察不清)</p> <p class="ql-block">  上面这个程序写得还算成功,但是我还不满足,我想让这八个WS2812按照这八个固定的颜色循环点亮,怎么办呢?想让某个灯的颜色固定不变,就不能改变GRB三个参数中的任意一个,也就是说,在那个一维数组的24个数据中,每三个组成固定搭挡,绝对是不能拆开,想要移动某个颜色数据,必须是GRB三个数据打包移动才行。比如我想让第一个WS2812变成橙色,必须让第二个WS2812显橙色的三个数据与第一个WS2812的显红色的三个数据调换位置才行。如此一来,最好的解决方案是把一维数组变成二维数组,然后利用C语言的指针进行换位操作更为方便。具体解决方案如下:(与上面程序相同的地方不再注释)</p><p class="ql-block"><br></p> <p class="ql-block">#include &lt;STC15.H&gt;</p><p class="ql-block">#include &lt;intrins.h&gt;</p><p class="ql-block">#define uint unsigned int</p><p class="ql-block">#define uchar unsigned char</p><p class="ql-block">sbit led_date=P3^4;</p><p class="ql-block">/*下面这个二维数组与上个程序的一维数组的原始数据完全一样,只是每三个数据三个数据打包,变成8行3列的二维数组*/</p><p class="ql-block">uchar color_array[][3]={</p><p class="ql-block"> {0x00,0xff,0x00}, /*红*/</p><p class="ql-block"> {0xff,0x61,0x00}, /*橙*/</p><p class="ql-block"> {0xff,0xff,0x00}, /*黄*/</p><p class="ql-block"> {0xff,0x00,0x00}, /*绿*/</p><p class="ql-block"> {0xff,0x00,0xff}, /*青*/</p><p class="ql-block"> {0x00,0x00,0xff}, /*蓝*/</p><p class="ql-block"> {0x00,0xff,0xff}, /*紫*/</p><p class="ql-block"> {0x8b,0x45,0x13}};/*微棕*/</p><p class="ql-block">/***************定义指针数组ary,里面包含8个指针,每个指针分别指向8种不同颜色数据的首地址*****************/</p><p class="ql-block">uchar *ary[8]={color_array[0],color_array[1],color_array[2],color_array[3],color_array[4],color_array[5],color_array[6],color_array[7]};</p><p class="ql-block">uchar *p; /*定义指针变量p,为下面的指针交换做交换容器*/</p><p class="ql-block">/******************下面这个函数是颜色交换函数*************************/</p><p class="ql-block">void change_color_array()</p><p class="ql-block">{</p><p class="ql-block"> uchar i; /*声明变量i*/</p><p class="ql-block"> for(i=0;i&lt;(sizeof(ary)/sizeof(ary[0]));i++) /*变量i从0增到7,因为有8个灯*/</p><p class="ql-block"> {</p><p class="ql-block"> /*指针交换核心代码*/</p><p class="ql-block"> p=ary[i];</p><p class="ql-block"> ary[i]=ary[i+1];</p><p class="ql-block"> ary[i+1]=p;</p><p class="ql-block"> }</p><p class="ql-block">}</p><p class="ql-block">void Delay200ms(void) //@24.000MHz</p><p class="ql-block">{</p><p class="ql-block"> unsigned char data i, j, k;</p><p class="ql-block"> _nop_();</p><p class="ql-block"> _nop_();</p><p class="ql-block"> i = 19;</p><p class="ql-block"> j = 62;</p><p class="ql-block"> k = 43;</p><p class="ql-block"> do</p><p class="ql-block"> {</p><p class="ql-block"> do</p><p class="ql-block"> {</p><p class="ql-block"> while (--k);</p><p class="ql-block"> } while (--j);</p><p class="ql-block"> } while (--i);</p><p class="ql-block">}</p><p class="ql-block">void ws2812_write_byte(uchar dat)</p><p class="ql-block">{</p><p class="ql-block"> uchar i;</p><p class="ql-block"> for(i=0;i&lt;8;i++)</p><p class="ql-block"> {</p><p class="ql-block"> if((dat&gt;&gt;(7-i))&0x01==0x01)</p><p class="ql-block"> {</p><p class="ql-block"> led_date=1;</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();</p><p class="ql-block"> led_date=0;</p><p class="ql-block"> _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_();_nop_();</p><p class="ql-block"> }</p><p class="ql-block"> else</p><p class="ql-block"> {</p><p class="ql-block"> led_date=1;</p><p class="ql-block"> _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_();_nop_(); </p><p class="ql-block"> led_date=0;</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); </p><p class="ql-block"> }</p><p class="ql-block"> }</p><p class="ql-block">}</p><p class="ql-block">/**********下面这个函数是写入颜色函数,与上个程序的颜色写入函数的区别是上个程序是通过下标直接引用一维数组的数据写入的,而这个函数是通过指针写入的,功能一样***************/</p><p class="ql-block">void _8_ws2812_write_color()</p><p class="ql-block">{</p><p class="ql-block"> uchar i,j;</p><p class="ql-block"> for(i=0;i&lt;8;i++) /*变量i从0到7自增*/</p><p class="ql-block"> {</p><p class="ql-block"> for(j=0;j&lt;3;j++) /*变量j从0到2自增*/</p><p class="ql-block"> {</p><p class="ql-block"> ws2812_write_byte(*(ary[i]+j));</p><p class="ql-block"> /*写入第i行第j列的数据*/</p><p class="ql-block"> }</p><p class="ql-block"> }</p><p class="ql-block">}</p><p class="ql-block">/***************主函数*************/</p><p class="ql-block"> void main()</p><p class="ql-block"> {</p><p class="ql-block"> while (1) /*无限循环*/</p><p class="ql-block"> {</p><p class="ql-block"> _8_ws2812_write_color();/*写入24个数据*/</p><p class="ql-block"> Delay200ms();/*延时,让LED点亮*/</p><p class="ql-block"> change_color_array();/*改变颜色顺序*/</p><p class="ql-block"> }</p><p class="ql-block"> }</p> <p class="ql-block">  好,点击编译,下载到单片机上运行一下看看效果:</p> <p class="ql-block">  还可以用16位的幻彩LED组成一个环,改变算法,弄出更炫酷的玩法哦:</p> <p class="ql-block">  考虑一下下面的程序:(仅测试用,限于篇幅,不再注释,但是有一点需要强调一下:,不同厂家生产的WS2812对写入时序的脉冲宽度稍有差别,比如我买的这个灯环对高电平与低电平的脉冲宽度比上面的WS2812更短一些,下面有标注。)</p> <p class="ql-block">#include &lt;STC15.H&gt;</p><p class="ql-block">#include &lt;intrins.h&gt;</p><p class="ql-block">#define uint unsigned int</p><p class="ql-block">#define uchar unsigned char</p><p class="ql-block">sbit led_date=P3^4;</p><p class="ql-block">uchar color_array[][3]={{0x01,0x01,0x01},{0x01,0x01,0x01},{0x01,0x02,0x01},{0x01,0x03,0x01},{0x01,0x04,0x01},{0x01,0x05,0x01},{0x01,0x06,0x00},{0x01,0x07,0x01},{0x01,0x08,0x01},{0x01,0x09,0x01},{0x01,0x0a,0x01},{0x01,0x0b,0x01},{0x01,0x0c,0x01},{0x01,0x0d,0x01},{0x01,0x0e,0x01},{0x01,0x0f,0x01}};</p><p class="ql-block">uchar *ary[16]={color_array[0],color_array[1],color_array[2],color_array[3],color_array[4],color_array[5],color_array[6],color_array[7],color_array[8],color_array[9],color_array[10],color_array[11],color_array[12],color_array[13],color_array[14],color_array[15]};</p><p class="ql-block">uchar *p;</p><p class="ql-block">void change_color_array()</p><p class="ql-block">{</p><p class="ql-block"> uchar i;</p><p class="ql-block"> for(i=0;i&lt;(sizeof(ary)/sizeof(ary[0]));i++)</p><p class="ql-block"> {</p><p class="ql-block"> p=ary[i];</p><p class="ql-block"> ary[i]=ary[i+1];</p><p class="ql-block"> ary[i+1]=p;</p><p class="ql-block"> }</p><p class="ql-block">}</p><p class="ql-block">void Delay50ms(void) //@24.000MHz</p><p class="ql-block">{</p><p class="ql-block"> unsigned char data i, j, k;</p><p class="ql-block"><br></p><p class="ql-block"> _nop_();</p><p class="ql-block"> _nop_();</p><p class="ql-block"> i = 5;</p><p class="ql-block"> j = 144;</p><p class="ql-block"> k = 71;</p><p class="ql-block"> do</p><p class="ql-block"> {</p><p class="ql-block"> do</p><p class="ql-block"> {</p><p class="ql-block"> while (--k);</p><p class="ql-block"> } while (--j);</p><p class="ql-block"> } while (--i);</p><p class="ql-block">}</p><p class="ql-block"><br></p><p class="ql-block"><br></p><p class="ql-block"><br></p><p class="ql-block">void ws2812_write_byte(uchar dat)</p><p class="ql-block">{</p><p class="ql-block"> uchar i;</p><p class="ql-block"> for(i=0;i&lt;8;i++)</p><p class="ql-block"> {</p><p class="ql-block"> if((dat&gt;&gt;(7-i))&0x01==0x01)</p><p class="ql-block"> {</p><p class="ql-block"> led_date=1;</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); /*差别在这里,高电平保持时间为18个机器周期,比上面买的WS2812B的20个_nop_()少了两个才能正确写入数据。*/</p><p class="ql-block"> led_date=0;</p><p class="ql-block"> _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();/*这里同样少两个机器周期*/</p><p class="ql-block"> }</p><p class="ql-block"> else</p><p class="ql-block"> {</p><p class="ql-block"> led_date=1;</p><p class="ql-block"> _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_(); </p><p class="ql-block"> led_date=0;</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();</p><p class="ql-block"> _nop_(); _nop_(); _nop_(); _nop_();_nop_(); _nop_(); _nop_(); _nop_(); </p><p class="ql-block"> }</p><p class="ql-block"> }</p><p class="ql-block"> </p><p class="ql-block">}</p><p class="ql-block">void ws2812_circle_write_color()</p><p class="ql-block">{</p><p class="ql-block"> uchar i,j;</p><p class="ql-block"> </p><p class="ql-block"> for(i=0;i&lt;16;i++)</p><p class="ql-block"> {</p><p class="ql-block"> for(j=0;j&lt;3;j++)</p><p class="ql-block"> {</p><p class="ql-block"> ws2812_write_byte(*(ary[i]+j));</p><p class="ql-block"> </p><p class="ql-block"> }</p><p class="ql-block"> }</p><p class="ql-block"> </p><p class="ql-block">} </p><p class="ql-block"> void main()</p><p class="ql-block"> {</p><p class="ql-block"> uint i,j,k=1,m;</p><p class="ql-block"><br></p><p class="ql-block"> while (1)</p><p class="ql-block"> {</p><p class="ql-block"> ws2812_circle_write_color();</p><p class="ql-block"> Delay50ms();</p><p class="ql-block"> change_color_array();</p><p class="ql-block"> switch(k)</p><p class="ql-block"> {</p><p class="ql-block"> case 0:</p><p class="ql-block"> m=2; break;</p><p class="ql-block"> case 1:</p><p class="ql-block"> m=0; break;</p><p class="ql-block"> case 2:</p><p class="ql-block"> m=1; break;</p><p class="ql-block"> case 3:</p><p class="ql-block"> k=0; break;</p><p class="ql-block"> }</p><p class="ql-block"> for(j=0;j&lt;=i;j++)</p><p class="ql-block"> {</p><p class="ql-block"> *(ary[j]+k)+=1;</p><p class="ql-block"> *(ary[j]+m)-=1;</p><p class="ql-block"> }</p><p class="ql-block"> </p><p class="ql-block"> if(i==16)</p><p class="ql-block"> {</p><p class="ql-block"> i=0;</p><p class="ql-block"> k++;</p><p class="ql-block"> </p><p class="ql-block"> }</p><p class="ql-block"> i++;</p><p class="ql-block"> }</p><p class="ql-block">}</p> <p class="ql-block">  编译下载之后,实际效果是这样的:</p> <p class="ql-block">  这么炫的幻彩LED灯环,边旋转边换色,你爱了吗?</p> <p class="ql-block">  嘿嘿,感觉效果还真不错!这种幻彩LED已经完全被我征服了!但是我还是觉得不满足,试想一下,假如有很多这种幻彩LED,配合多种不同的算法,再配个爆炸脉冲发生器、烟花音效发生器、大功率音频放大器,搞个电子烟花不是很酷的吗?这个想法真不错,以后有钱一定搞一套!</p>