3.2 程序设计思路
大家最关心的想必是怎样在这个迷你光立方上实现自己喜欢的3D动画。其实光立方的图形实现跟数码管的扫描是一样的,都是把要显示图形的数据送到对应的I/O口上面,然后再用单片机快速切换扫描每一层的数据,由于每一层切换的时间非常快,整体看来就是一个图形了,好多电子爱好者的光立方都是这么实现的。考虑到要实时响应按键开关的切换以及动画的显示速度,动画显示函数我是在定时器中断里实现的,而不是用之前的,比如“delay()”这种软件延时方式去扫描,这样做就可以最大化响应用户的按键操作了。为了更好地去编辑自己的动画显示,我写了一个可以显示任何动画的函数,内容如下:
void figureshow(uchar a[], uchar b[], uchar n) { P1=P1|0x0f; //先关闭 count++; if(count>ledspeed) { count=0; if(j<n) //n代表一个显示数组总共有多少个元素 { for(i=0; i<4; i++)//刷新一帧动画 { dis2[i]=a[j]; //每层显示内容 dis3[i]=b[j]; //4个数据一组 j++; } } else {j=0; count=ledspeed; } } P2=dis2[num]; //送入显示数据 P3=dis3[num]; P1=P1&ledcom[num]; //切换扫描每一层 num++; num%=4; //限制在4以内 }
在使用函数之前,先来看看LED方阵的连接。为了方便操作,我把每个LED的控制引脚都标出来了,箭头的方向就是我们正面观看的方向(见图3.2)。
图3.2 LED方阵的连接
LED的方阵是由P2组跟P3组控制的,这两组的I/O口设置为推挽输出,这样把要显示的对应点设置为高电平“1”,就可以点亮了。那么如何使用这个函数呢?每一帧的动画是由4层方阵组成,比如我们要设计动画的第一帧是全亮,我们先定义两个数组,这两个数组分别由P2组和P3组输入:
code uchar All_P2[]={//全闪烁 0xff,0xff,0xff,0xff/*1*/ }; code uchar All_P3[]={ 0xff,0xff,0xff,0xff/*1*/ };
两个数组里面各有4个元素,每组第一个元素代表第一层的显示,第二个元素代表第二层的显示,以此类推,到了第四个元素就完成这一帧的显示了。函数的n代表每个数组的元素个数。
使用的时候这样操作:figureshow(All_P2, All_P3,4);就可以全亮了!这只是简单的使用,因为动画是要“动”起来的,也就是每一帧的数据都不同才可以,我们要设计一个动画,就要考虑好这个动画的帧数,然后把每一帧的数据编写出来,放在这个函数里面就可以显示动画了!当要修改动画的快慢(也就是帧数的转换速度)时,把Iedspeed修改为不同的值即可。注意,这个函数要放在定时器中断里面,要设置时间就要修改中断的累积时间计数值。程序中我设置了短按控制来转变动画时间的快慢,关于按键的长按、短按已经在之前的文章中有了比较详细的说明,在这里我就不多介绍了。还有一点要补充说明的是,为什么在数组前面加code呢?因为我们要显示的动画有很多种,为了节省RAM的资源,所以把数组的数据放在ROM上面。
我不是“标题党”,为了使光立方有种“梦幻”的效果,我加入了各种形式的呼吸灯模式,也就是逐渐变亮、变暗的效果,专业名词就是脉冲宽度调制(也就是PWM)。因为直接用软件循环的语句很难控制PWM的周期,在这里,我们用定时器0产生特定周期的PWM,从而控制亮度。我们先定义一个10ns的定时器中断,注意中断一定要快,由于人的视觉暂留现象,LED就会出现不同的亮度。然后在中断里面进行以下操作:
void timer0() interrupt 1 { P1=P1|0x0f; //先关闭 Pcount++; if(Pcount>254) //每中断255次产生一种亮度 { Pcount=0; } if(Pcount<levelcount) //修改levelcount的数值就可以修改 不同亮度 { P2=0xff; //在这段数值内全亮 P3=0xff; } else { P2=0x00; P3=0x00; } P1=P1&ledcom[num]; num++; num%=4; }
用通俗的语言来讲,就是在快速扫描的时间段内,亮的次数比灭的次数要多,这样就可以产生不同的亮度了。修改if(Pcount<levelcount)语句里面P2组与P3组所控制的LED数,就可以设置不同形式的呼吸灯了!但是上面的中断函数只能产生一种亮度,修改levelcount的值就可以修改不同亮度,levelcount的数值范围是0~255,也就是有256级的亮度。而一种亮度显然不能达到我们所需要的“呼吸”效果,因此我们要在其他地方让levelcont自动增加数值到最高值,之后又逐渐减少数值到最小值,来回循环,这样就有呼吸的效果了!
if(levelfl ag)//初始值为1 { levelcount++; //亮度逐渐变大 } else { levelcount--; //否则亮度逐渐降低 } if(levelcount>254) //到达亮度最大值时转变为逐渐降低亮度 { levelfl ag=0; } if(levelcount==0) //亮度为最低时转变为逐渐增加亮度 { levelfl ag=1; }
首先定义一个开始转变亮度的标志位levelflag,初始化的值为1, levelcount每次到达最高点以及最低点都翻转IeveIfIag的数值。把上面的程序放在你需要设置的转变亮度时间内,就能实现“梦幻呼吸灯”的效果了!
再来说一下声控效果的实现。其实简单来说,就是用驻极体话筒采集、放大声音,然后转变为电压值,再用STC15F204EA内置的10位AD功能读取电压值。驻极体话筒的放大电路可参考我画的电路图,单片机内部的10位AD读取函数可以参考单片机的数据手册。不过,检测到声音也不能让光立方一直亮着呀,最好是检测到声音的不同大小、长短会有不同层次的亮起,让光立方“显示”出声音的级别,这样就会更加有动感了。只需要进行下面的简单操作即可实现不同级别声音显示不同的效果了:
ADValue=ADC(7); //先读取P1.0的AD值 if(ADValue>1) //判断AD值是否大于规定的值 { P1=P1|0x0f; //先关闭 P2=0xff; //每层全亮 P3=0xff; P1=P1&ledcom[num]; num++; num%=4; } else { P1=P1|0x0f; //先关闭 P2=0; //每层灭灯 P3=0; P1=P1&ledcom[num]; num++; num%=4; }
将上面那段程序放到定时器中断里扫描,由于要检查到AD值大于1才能进入扫描,当声音很小或者很短的时候,就只能扫描到一两层的LED方阵,同理,声音持续的时候,就能不断进行LED方阵的扫描。由于扫描速度很快,声音大的时候,显示层数就多,声音小的时候,显示层数就少,用这种简单的操作就可以“看”出来声音的大小了。要是你在一旁“high歌”,光立方就随乐而动,很有动感!
好了,上述算是这个制作的精髓了,其实大家了解深透的话,就可以举一反三,用到更多的工程上面了。趁着将要过年,如果能亲手打造一个属于自己的光立方放在客厅里,客人来拜访,看到随声而动的光立方,一定会增加不少乐趣!
最后来看看几种夜晚的显示效果吧(见图3.3~图3.5)!
图3.3 “闪电”式流动
图3.4 呼吸灯状态,可以看到所有的LED的“呼吸”过程
图3.5 “龙卷风”转动模式,很有立体感
实际视频操作效果见:http://hi.baidu.com/haorongwu。