项目概述
我们用Tiny6410开发板,利用本学期的学习内容完成一个数码相册的设计,希望它可以用LCD显示图片,有多张图片可以用来切换,可以定时播放图片,可以用小键盘来控制图片的播放,如播放前一张、播放后一张、开始自动播放、停止自动播放等功能。经过我们两个人的努力,设想的功能已经全部实现,并测试通过了。
项目人员组成及分工
- 王佳琦:负责LCD绘图和打印字符
- 谢昌秦:负责处理中断
项目效果
- 开机默认自动播放模式
- 切换到第二张图
- 第二张图
- 切换到第三张图
- 第三张图
- 又回到第一张图
项目开发过程
我们的开发过程主要是从帧入手的,一开始我们实现的是定时器,使我们指定的一个函数可以每秒都被调用一定次数,然后插入图片数据到代码中,再实现动画过渡效果。最后,我们增加了按键中断,从而可以通过按键进行播放暂停、上一张和下一张等功能。
关于图像,首先计算出每张图片的大小,然后用画图工具画出3张符合的BMP图片。然后根据BMP文件格式,24-bits的BMP文件,除了前54位文件头,剩下的都是像素信息,又因为计算出来的长度刚好是4的倍数,所以不用考虑填充值。用C++语言代码将获取到的像素值用十六进制打印出来,然后存成二维数组,为以后绘图做准备。
了解并学习第十九章LCD绘图和打印字符中提供的参考代码22.lcd.在lcd_init()对lcd控制器进行了初始化,配置相关引脚用于LCD功能,配置MIFPCON寄存器,选择normal mode,配置SPCON寄存器,选择RGB I/F,配置VIDCONx,设置接口类型、时钟、极性和使能LCD控制器,配置VIDTCONx, 设置时序和长宽等,配置WINCON0,设置window0的数据格式,配置VIDOSDOA/B/C,设window0的坐标系,配置VIDWOOADD0B0和VIDWOOADD1B0,设置framebuffer的地址。
项目总结
本次项目的需求看起来是非常简单的,涉及到的基本都是学过的内容,但是具体到实现过程中,却发现遇到了很多问题。接下来列举出我们遇到的问题。
首先遇到的问题是图片放置的问题。由于我们没有学怎么读写SD卡,而资料里、网络上都很难找到例子。对于我们这样的嵌入式开发初学者来说,没有实例代码,从文档入手写出实现某个功能的代码是很困难的,因为文档不一定完整,很容易遇到坑,而我们对嵌入式开发的经验也几乎为零。于是,我们打算曲线救国,将图片的颜色数据直接作为常量硬编码到代码中。一开始,我们按照LCD显示屏的大小,准备了3张480*272
像素的图片,并分别用工具转换成对应的数组数据。但是,当我们尝试将生成的bin文件写入到开发板的时候,却发现MiniTools的日志中爆了错误,而启动之后发现程序也没有发现更改(我们一开始使用的是纯色作为图片)。后来我们发现,大概是bin文件过大的缘故,烧写直接失败了,所以程序才没有发生改变。接下来,我们删除了一张照片,发现仍然无法烧写。于是,我们将尺寸缩小到长、宽各为原来大小的四分之一,发现此时3张照片的大小之和还不如原来一张照片!我们还计算了变为原来三分之一的情况,但是发现这种情况下照片的数据空间减少不明显,于是我们最终的解决方案是使用120*68
大小的三张照片,并在每个页面里将照片平铺成4*4
的矩阵。恰好我们选择的照片都是纹理类的,平铺之后效果还是不错的!
然而,接下来我们有遇到了新的问题:原生无法进行浮点数和整数除法运算。发现这个问题的情况十分有趣,因为一开始的我们并没有发现渲染一屏幕的图像需要很长时间,于是我们打算设计一段炫酷的照片过渡动画,我们计划每秒25帧,然后使用25帧的时间用来显示过渡动画,于是我们据此进行了一番计算,得出了每一帧里每个像素填充的是来自原来照片里哪个像素的颜色,这其中涉及到了向量、矩阵变换等,但是还没有等我们验证是否我们的计算正确,问题就来了:一运行到过场动画的时候,程序就卡死了。我们通过注释代码的方式查找问题所在,最后发现一个有趣的现象:虽然我进行了大量的浮点数运算,但是只要涉及到对这些运算结果的使用(包括绘制到LCD上、通过printf
输出等),就会卡死。最终,我们综合网络上查找的结果,得出的结论是:需要使用特殊的配置才能利用CPU中的浮点数运算,而如果没使用这些结果,那么在编译的过程中,这些内容会被优化掉而避免了卡死。
这就很尴尬了,因为如果避免使用浮点数,用整数运算代替的话,运算结果将会产生大量误差,这会招致效果很糟糕,于是,在苦苦搜索一番无果之后,我们放弃了这个方案。事实证明,放弃这个方案是正确的,因为即使CPU支持浮点数运算,后面仍然有问题等着我们。
我们接下来遇到的问题最让人头疼,我们被迫做了很多妥协,那就是这块屏幕(也可能是CPU)的性能实在堪忧!我们发现,绘制整个屏幕的内容的话,需要几乎一秒钟时间,能够明显看到这一屏幕的图案是按照代码中的遍历顺序刷新的,这导致我们后来放弃了其他不涉及浮点数但是需要重新绘制整个屏幕的过渡动画(如颜色渐变),被迫只能使用能最大化优化的过渡动画。最终,我们选择的动画是滑动覆盖,类似下一张照片是一幅画卷,从两侧或一侧慢慢反卷展开,这种过渡动画的优势在于每个相邻帧之间,只有一小段图案需要重绘,这就给我们大大增加了效率,从而使得动画勉强有了点动画的样子……然而,即使在此时,我们也遇到了问题:我们发现编译的时候无法进行整数除法!我们经过一番排除之后,发现原因在于编译器原生不支持硬件的整数除法。但是我们的动画会在25帧中进行,我们会涉及到除以24的情况,这就没有办法用位移解决了。我们这次终于在网络上找到了解决方案:我们需要使用一个编译器提供的lib文件,里面包含了除法的软件实现。然而引入之后发现还缺少了一个函数的定义,原来这个函数只是一个空函数,于是我们新增了一个文件特意定义了这个函数,终于通过了编译并能够成功使用这些功能了。
最后,还需要提一提我们一开始遇到的定时器的问题。由于决策失误,我们一开始计划使用RTC的方式提供定时功能,通过每秒触发若干次中断的方式驱动每一帧的前进,但是范例中只提供了如何获取BCD码的年月日时分秒,并不能精确到毫秒,我们折腾了好久,最后还是放弃了,更换了PWM定时器。虽然一开始我们计划的是每秒25帧,但是由于PWM的频率要求,我们只能设置为了每秒32次中断。当然,这个影响是微乎其微的,因为实际上,由于LCD的效率低下,每一帧的时间都被拉长了……
最后一个问题是按键中断的抖动问题。在我们最开始接触按键中断的时候,我们就已经发现,不论使用哪种触发方式,都不可避免大量毛刺,导致依次按下会引发多次中断,这对于上一张、下一张这样的功能是没有问题的,因为我们在代码中进行了判断,如果当前没有播放动画,才会开始播放动画(设置相应的变量来区分);如果当前正在播放过渡动画,那么会直接结束中断的处理。然而,对于播放和暂停而言,却没有做区分,而我们又在用一个按键上设置了播放、暂停的切换,这就导致按下的时候很容易变成连续按下多次的效果,而使得播放和暂停失效,这肯定是无法忍受的,于是我们查找了很久如何滤波。然后,虽然我们进行了相应的设置,但是完全没有效果,依然会有多次触发。最后,我们再一次“妥协”:通过软件进行过滤。具体的实现方法是增加一个布尔变量,我们实现决定一帧只能接受一次按键中断,所以这个布尔变量每一帧的开始会被置为真,但每当按键中断到来的时候,我们就会把这个变量置为假,以此屏蔽后续的抖动带来的额外的中断信号。实践发现,效果非常好!
说完了我们遇到的问题,我们就说说我们整个项目的结构。我们在设计之初的时候,是这样想的:计划好每秒会运转若干帧,而每一帧都会触发一个函数,对相关的变量的值进行刷新,并按需要刷新LCD。而按键带来的中断也会进行相应的操作。最重要的是,我们将LCD的刷新设计为根据变量的内容来变化,换言之,我们维护了一系列变量,这些变量表示了当前LCD的状态,他们只会在每一帧到来时、按键中断到来的时候被修改,因此对应了LCD的内容的改变。而每一帧再通过这些状态的值来决定如何重绘,这大大降低了开发成本,提高了效率。为什么这么说呢?因为我们一开始的时候,只设计了“下一张”一个功能。后来,我们计划追加上一张、播放/暂停功能。对于前者,我们只要修改代表下一张照片编号的变量即可,而对于后者,我们只需要限制代表当前帧序号的变量不再继续递增,即可很容易控制LCD的更新。
除此以外,还有一点值得说的是,为了提高效率,我们特意实现了一个程序,读入BMP照片并生成对应的C语言语法的静态数组变量语句,这为我们插入图片数据带来了极大的便利,可见有时候,为了主要的开发任务而进行的额外的开发任务也是很有意义的。
我们再探讨一下后面的改进方向。我们认为,我们目前的项目的改善方向主要是性能和自定义方面的改善。对于前者,我认为我们应该更多的发掘一些更加酷炫但是能够尽可能减少渲染像素的过渡动画。我们认为,CPU的运力应该是远超过LCD的绘制效率的,因此,我们可以尽可能尽量增加运算,从而得出最少的需要重绘的像素,达到性能的最优化。对于后者,我们的想法是,虽然我们目前无法从SD卡读取图片,但是我们可以尝试改进我们的小程序,能读取任意照片并等比例缩放到小的尺寸,并计算出相应的像素数据。这样一来,我们就可以通过很少的步骤替换程序中的照片,甚至通过分析bin文件结构的方式,不需要重新编译就能进行热替换。当然,正道还是探讨如何读取SD卡的内容……
最后,是我们经过这次项目所获得的感受。嵌入式开发和平时我们的程序非常不一样,因为嵌入式系统往往都是实时的,我们的程序除了中断以外就不会被暂停(不像其他系统,如Windows,CPU是被分割成很多时间片来使用的),这也是为什么几乎所有程序都要在main函数中写一个死循环,避免程序结束。另外,嵌入式平台的性能也非常非常有限,限制随处可见:LCD的绘制速度、CPU的运算速度、编译器(CPU)支持的运算甚至到bin文件的大小等,都有很多严苛的限制,开发的难度比普通的PC程序开发要大得多,挑战也更加大。
Enjoy!