雪花板学习心得
本项目是在学习基于8051单片机控制的WS2812全彩LED 雪花板时的一些心得体会,以及最终小小的项目分享。本次分享中会包含一些学习过程中debug的过程,以便更加清晰的解释WS2812内部的工作原理,以及记录一些仍然存在的困惑。
标签
嵌入式系统
显示
8051
LED
Thrshbia
更新2020-12-30
1006

本次项目使用到的雪花板是在硬禾实训营中进行嵌入式入门时所学习的一个基于STC15W20XS(8051内核)单片机的学习板。为了方便初学者使用串口与PC进行通信或下载程序,雪花板使用了沁恒CH340USB转串口芯片,外部通信和供电接口为microUSB。其中,雪花板上最重要的部分就是37个WS2812芯片。该芯片将RGB全彩灯和控制电路集成在一起,通过串接级联的方式进行连接。

FhfssNAcWnCHolOq2JBTnFwMCB-Q

Fp1RwoyMqEshijndUKHlHJhKNy0I

学习一个芯片最重要的资源就是它的数据手册。对于WS2812芯片来说,数据手册中详细说明的单片机与该芯片之间的通信模式和编码方式。其中每一个WS2812芯片一次接受24Bit的数据,该24Bit数据可被分为3个8位数据,依次表示G(Green)R(Red)B(Blue), 数据从高位开始接受。通过高低电平不同的占空比来表示0码和1码,拉低数据管脚一段时间来RESET(实际测试发现与数据手册描述的略有差异,这部分会放在后面详述)。当多个芯片依次级联,每个WS2812被当作一个像素点,每次重新RESET后,当一串连续变化的数据流由单片机发出进入第一个WS2812的DIN,该芯片会截获前24位的数据,然后通过进步集成的自动整形放大电路将剩余的数据发出到下一个级联的的WS2812芯片DIN,依此类推。

以上是通过原理图和数据手册对雪花板实现原理的一些理解和解读,接下来我想试图解释一下小马老师提供的代码模板中RESET函数的实现原理和我学习过程中的总结。

ResetDataFlow函数实现了WS2812芯片的RESET,函数内部非常直接的先拉低DIN管脚(输出低电平0),然后通过两个for loop让单片机空转实现延时。

void ResetDataFlow(void)
{
	unsigned char i,j;
	DI=0;				
	for(i=0;i<50;i++)		
	{
		for(j=0;j<20;j++)
		{
			;
		}
	}
}

这里有一个一开始容易出现的误解(非常感谢小马老师的纠正和指导)我们会认为如果设置33M的时钟频率,对于该款8051单片机,执行一条指令只需要一个时钟周期,因此每条语句耗时1/33M=0.03us。因此代码中20*50=1000个时钟周期的循环让DIN管脚有大约0.03us*1000=30us的持续低电平。然而,这样计算是不准确的。

首先,我想区分一下指令周期,机器周期和时钟周期的概念。

时钟周期T,又称振荡周期,常定义为时钟脉冲频率(时钟主频)的倒数,是时序中最小的时间单位。例如,如单片机时钟频率为1MHz,则它的时钟周期T应为1μs。

机器周期,把一条指令的执行过程划分为若干个阶段(如取指、译码、执行等),每一阶段完成一个基本操作。完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由若干个时钟周期组成。对于我们使用的这款STC15W24单片机,一个机器周期仅需要一个时钟周期,相比于老款的8051单片机一个机器周期需要12个的时钟周期,该单片机的运行速度已经大大提升。

指令周期,是指CPU(单片机)执行完一条具体的指令所需要的时间,不同的指令需要不同的机器周期来完成。对于nop指令(汇编中的空指令,让单片机发呆一个机器周期)只需要一个机器周期,而对于其它的复杂指令可能需要多个机器周期(或时钟周期)来完成。

现在,我们可以知道for loop每运行一次可能不只单单的执行一条语句,实际上即便我们for loop里什么都不加,循环变量i,j的赋值,比较和递增都需要不等的机器周期来执行,因此我们很难通过这样的方法来计算出来实际的延时时间。

那么我们如果想实现准确的延时应该怎么办呢?

小马老师教了我们两种方法,第一种方法便是用_nop()指令来进行一个时钟周期的准确延时,这个方法也被应用到了SendOnePix()的函数中。

第二种更为方便的方法是(老师偷偷告诉我的), 在宏晶STC官方的下载器上有自带的延时计算功能,能够自动生成延时函数,直接拷贝到自己的代码里就可以了。

Ftcqd5xBE7MkDYJbaKFzKm9ZEn1X

另外,如果大家以后需要实际测出某一段代码运行的时间,或者想试一下小马老师代码里的延时函数到底是不是如他所说(hhh), 可以用逻辑分析仪进行分析。

其余的几个重要函数,WS2812_Close()比较简单就是连续发好多个0码让全部的37个灯都熄灭,而SendOnePix()最重要的延时的实现已经在上一部分讲过,是通过_nop()实现的。

void WS2812_Close(void)
{
	unsigned char count_sum;
	for(count_sum=0;count_sum<37;count_sum++)
	{
		SendOnePix(close);
	}
	ResetDataFlow();
}
void SendOnePix(unsigned char *ptr) //unsigned char a[3]={0,0,50};0=0x00=0000 0000   0x80=1000 0000
{
	unsigned char i,j;
	unsigned char temp;

	for(j=0;j<3;j++)
	{
		temp=ptr[j];
		for(i=0;i<8;i++)
		{
			if(temp&0x80)		 //从高位开始发送
			{
				DI=1;			 //发送“1”码
				_nop_();		 //不可省略的nop(),延时指定时间作用,晶振频率33MHz
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();//一个指令周期,对于1T,一个周期就是一个时钟周期,33ns
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();

				DI=0;
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
			}
			else				//发送“0”码
			{
				DI=1;
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();

				DI=0;
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
				_nop_();
			}
			temp=(temp<<1);		 //左移位
		}
	}
	//DI=1;
}

其它的亮灯的花样,就如小马老师有曰:剩下的就是逻辑的问题了,看你们的创造力了。

但是有一点需要注意由于PCB布线的限制,像素点(WS2812)的级联顺序不是按照由内而外一圈一圈连接的,而是一条直线一条直线的连接最后连接到中心的那一个。为了方便理解我会按顺序依次点亮,方便大家理解。

	while(1)
	{
    	 for(count_sum=0;count_sum<=36;count_sum++)		
		{	
		    for(count=0;count<=count_sum;count++)
			{
				SendOnePix(color[(count)%7]);
			}
			Delay100Ms(2);
		 }
		 ResetDataFlow();
		 WS2812_Close();
		 ResetDataFlow();

         } 

现在,我想说一下,我对WS2812这个芯片RESET的个人见解和惊人的发现。(大家可以自己试一下)

  1. 无论拉低还是拉高DIN管脚超过50us,WS2812芯片都会自动RESET。
  2. 可以用任何超过50us的延时代替RESET函数,不用刻意的拉低DIN电平(一开始以为是由于SendOnePix函数最后将DIN拉低了,因此之后再进行延时相当于已经拉低了DIN;但是,后来测试如果在延时前先将DIN拉高,不会影响结果;再之后,我直接将ResetDataFlow内将DIN拉低的语句改为拉高,仍然不会影响效果)
void ResetDataFlow(void)
{
	unsigned char i,j;
	DI=1;					//DI置为0后,延时50us以上,实现帧复位
	for(i=0;i<50;i++)		//此处33Mhz时延时65us
	{
		for(j=0;j<20;j++)
		{
			;
		}
	}
}

可以看出,原来ResetDataFlow函数中,DI=0的语句被修改为了DI=1,目的是看一下如果我们用拉高DIN端口的方式复位(而不是数据手册上所说拉低DIN)最终的亮灯效果会不会有什么变化。

最终发现,除了有一个灯偶尔会莫名其妙的亮蓝灯之外,其余的效果基本没有变化。

成果展示:

我最终的项目成果是期望做出一个有礼花效果的LED阵列。实现方式为:从雪花板中心开始向外辐射,每辐射一圈结束会闪烁当前辐射的最外圈;当辐射到第五圈(最外圈时),雪花板37个LED灯全亮且以红,橙,黄,绿,蓝,紫, 白七种颜色交错排列,闪烁七次,每次闪烁都会改变每个LED的颜色。闪烁结束,雪花板会从外层开始以原先1/4的速度向内收缩辐射,用以模拟礼花绽放后的散落过程。

效果展示:

代码展示:

void  main()
{
	Delay100Ms(10);			  //上电延时
 	ResetDataFlow();	ResetDataFlow();
 	WS2812_Close();
	//主循环
	while(1)
	{


		Delay100Ms(10);
		fireworks();
		ResetDataFlow();
		WS2812_Close();
		Delay100Ms(3);

	}
}

 void fireworks()
{
    unsigned char count=0;
	unsigned char count_sum;
	unsigned char i=0,j,m=0,n;

	for(j=0;j<5;j++)
	{
		for(i=0;i<=j;i++)
		{
			WS2812_1_5Line_In(color[(i+j+m)%7],i+1);
			Delay100Ms(1);
		}
		   
			switch(j)
			{
			case 5:
					WS2812_1_5Line_In(color[i],5);
			break;
	
			case 4:
					WS2812_1_5Line_In(color[i],4);
			break;
	
			case 3:
					WS2812_1_5Line_In(color[i],3);
			break;
	
			case 2:
					WS2812_1_5Line_In(color[i],2);
			break;
	
			case 1:
					WS2812_1_5Line_In(color[i],1);
			break;
			
			default:
					WS2812_1_5Line_In(color[j],1);
			break;			
		}
			DelayMs(50);
	
	}

		if(count==6) count=0;
		else count++;

		Delay100Ms(1);
		WS2812_Close();	 
		Delay100Ms(1);
		//
	 for(n=0;n<7;n++)
	{			
		 for(count=0;count<=36;count++)
			{
					SendOnePix(color[(count+n)%7]);
			}
		Delay100Ms(2); 
		
	}
	for(j=5;j>0;j--)
	{
		for(i=0;i<j;i++)
		{
			WS2812_1_5Line_In(color[(i+j+m)%7],i+1);
			DelayMs(50);
		}
		   
			switch(j)
			{
			case 5:
					WS2812_1_5Line_In(color[i],5);
			break;
	
			case 4:
					WS2812_1_5Line_In(color[i],4);
			break;
	
			case 3:
					WS2812_1_5Line_In(color[i],3);
			break;
	
			case 2:
					WS2812_1_5Line_In(color[i],2);
			break;
	
			case 1:
					WS2812_1_5Line_In(color[i],1);
			break;

			default:
					WS2812_1_5Line_In(color[j],1);
			break;
						
		}
			DelayMs(50);
	 }
	 
	 	
 }

以上就是我这次雪花板项目的分享,很多仅为自己的理解,如果错误欢迎留言指正,谢谢。

再次感谢苏老师和马老师的耐心指导和帮助。

团队介绍
西交利物浦大学,硬禾实战营学员
团队成员
薛嘉琪
机械电子工程,大三学生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号