电子说
7.5 点阵的动画显示
点阵的动画显示,说到底就是对多张图片分别进行取模,使用程序算法巧妙的切换图片,多张图片组合起来就成了一段动画了,动画片、游戏等等基本原理也都是如此。
7.5.1 点阵的纵向移动
7.4节介绍了如何在点阵上画一个❤形,有时候希望这些显示是动起来的,而不是静止的。对于点阵本身已经没有多少的知识点可以介绍了,主要就是编程算法来解决问题。比如现在要让点阵显示一个I ❤ U的动画,首先要把这个图形用取模软件画出来看一下,如图7-10所示。
图7-10 上下移动横向取模
这张图片共有40行,每8行组成一张点阵图片,并且每向上移动一行就出现了一张新图片,一共组成了33张图片。
用一个变量index来代表每张图片的起始位置,每次从index起始向下数8行代表了当前的图片,250ms改变一张图片,然后不停的动态刷新,这样图片就变成动画了。首先要对显示的图片进行横向取模,虽然这是33张图片,由于每一张图片都是和下一行连续的,所以实际的取模值只需要40个字节就可以完成。
#include
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code image[] = { //图片的字模表
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3,0xFF,
0x99,0x00,0x00,0x00,0x81,0xC3,0xE7,0xFF,
0x99,0x99,0x99,0x99,0x99,0x81,0xC3,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
void main()
{
EA = 1; //使能总中断
ENLED = 0; //使能U4,选择LED点阵
ADDR3 = 0;
TMOD = 0x01; //设置T0为模式1
TH0 = 0xFC; //为T0赋初值0xFC67,定时1ms
TL0 = 0x67;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
while (1);
}
/* 定时器0中断服务函数 */
void InterruptTimer0() interrupt 1
{
static unsigned char i = 0; //动态扫描的索引
static unsigned char tmr = 0; //250ms软件定时器
static unsigned char index = 0; //图片刷新索引
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
//以下代码完成LED点阵动态扫描刷新
P0 = 0xFF; //显示消隐
switch (i)
{
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=image[index+0]; break;
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=image[index+1]; break;
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=image[index+2]; break;
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=image[index+3]; break;
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=image[index+4]; break;
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i++; P0=image[index+5]; break;
case 6: ADDR2=1; ADDR1=1; ADDR0=0; i++; P0=image[index+6]; break;
case 7: ADDR2=1; ADDR1=1; ADDR0=1; i=0; P0=image[index+7]; break;
default: break;
}
//以下代码完成每250ms改变一帧图像
tmr++;
if (tmr >= 250) //达到250ms时改变一次图片索引
{
tmr = 0;
index++;
if (index >= 33) //图片索引达到33后归零
{
index = 0;
}
}
}
将这个程序下载到单片机上看看效果,即可看到I ❤ U一直往上走动的动画。
7.5.2 点阵的横向移动
上下移动会了,那还想左右移动该如何操作呢?
方法一、最简单的办法就是把板子侧过来放,纵向取模。
要做好技术,但是不能沉溺于技术。技术是工具,在做开发的时候除了用好这个工具外,也得多拓展自己解决问题的思路,要慢慢培养自己的多角度思维方式。
那把板子正过来,左右移动就完不成了吗?当然不是。一旦硬件设计好了,要完成一种功能,大脑就可以直接确定能否完成这个功能,这在进行电路设计的时候尤为重要。开发产品的时,首先是设计电路,此刻就要在大脑中通过思维来验证板子硬件和程序能否完成想要的功能。一旦硬件做好了,剩下的就是靠编程来完成了。只要是硬件逻辑上没问题,所有的功能均可由软件实现。
在进行硬件电路设计的时候,也得充分考虑软件编程的方便性。因为程序是用P0来控制点阵的整行,所以对于这样的电路设计,上下移动程序是比较好编写的。那如果设计电路的时候知道图形要左右移动,那设计电路画板子的时候就要尽可能的把点阵横过来放,有利于编程方便,减少软件工作量。
方法二、利用二维数组来实现,算法基本上和上下移动相似。
二维数组的声明方式是:
数据类型 数组名[数组长度1][数组长度2];
与一维数组类似,数据类型是全体元素的数据类型,数组名是标识符,数组长度1和数组长度2分别代表数组具有的行数和列数。数组元素的下标一律从0开始。
例如:unsigned char a[2][3];声明了一个具有2行3列的无符号字符型的二维数组a。
二维数组的数组元素总个数是两个长度的乘积。二维数组在内存中存储的时候,采用行优先的方式来存储,即在内存中先存放第0行的元素,再存放第一行的元素......,同一行中再按照列顺序存放,刚才定义的那个a[2][3]的存放形式就如表7-1所示。
表7-1 二维数组的物理存储结构
| a[0][0] | a[0][1] | a[0][2] | a[0][2] | a[1][1] | a[1][2] |
二维数组的初始化方法分两种情况,前边一维数组讲过,数组元素的数量可以小于数组元素个数,没有赋值的会自动给0。当数组元素的数量等于数组个数时,如下所示:
unsigned char a[2][3] = {{1,2,3}, {4,5,6}}; 或者是
unsigned char a[2][3] = {1,2,3,4,5,6};
当数组元素的数量小于数组个数的时候,如下所示:
unsigned char a[2][3] = {{1,2}, {3,4}}; 等价于
unsigned char a[2][3] = {1,2,0,3,4,0};
而反过来的写法
unsigned char a[2][3] = {1,2,3,4}; 等价于
unsigned char a[2][3] = {{1,2,3}, {4,0,0}};
此外,二维数组初始化的时候,行数可以省略,编译系统会自动根据列数计算出行数,但是列数不能省略。
讲这些,是为了看别人写的代码的时候别发懵,但是今后自己写程序要按照规范,行数列数都不要省略,全部写齐,初始化的时候,全部写成unsigned char a[2][3] = {{1,2,3}, {4,5,6}};的形式,而不允许写成一维数组的格式,防止大家出错,同时也是提高程序的可读性。
那么下面要进行横向做I ❤ U的动画了,先把需要的图片画出来,再逐一取模,和上一张图片类似的是,这个图形共有30张图片,通过程序每250ms改变一张图片,就可以做出来动画效果了。但是不同的是,这个是要横向移动,横向移动的图片切换时的字模数据不是连续的,所以这次要对30张图片分别取模,如图7-11所示。
图7-11 横向动画取模图片
图7-11中最上面的图形是整个连续图形,实际上要把它分解为30个帧,每帧图片单独取模,取出来都是8个字节的数据,一共就是30*8个数据,用一个二维数组来存储它们。
#include
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code image[30][8] = {
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}, //动画帧1
{0xFF,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F}, //动画帧2
{0xFF,0x3F,0x7F,0x7F,0x7F,0x7F,0x7F,0x3F}, //动画帧3
{0xFF,0x1F,0x3F,0x3F,0x3F,0x3F,0x3F,0x1F}, //动画帧4
{0xFF,0x0F,0x9F,0x9F,0x9F,0x9F,0x9F,0x0F}, //动画帧5
{0xFF,0x87,0xCF,0xCF,0xCF,0xCF,0xCF,0x87}, //动画帧6
{0xFF,0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3}, //动画帧7
{0xFF,0xE1,0x73,0x73,0x73,0xF3,0xF3,0xE1}, //动画帧8
{0xFF,0x70,0x39,0x39,0x39,0x79,0xF9,0xF0}, //动画帧9
{0xFF,0x38,0x1C,0x1C,0x1C,0x3C,0x7C,0xF8}, //动画帧10
{0xFF,0x9C,0x0E,0x0E,0x0E,0x1E,0x3E,0x7C}, //动画帧11
{0xFF,0xCE,0x07,0x07,0x07,0x0F,0x1F,0x3E}, //动画帧12
{0xFF,0x67,0x03,0x03,0x03,0x07,0x0F,0x9F}, //动画帧13
{0xFF,0x33,0x01,0x01,0x01,0x03,0x87,0xCF}, //动画帧14
{0xFF,0x99,0x00,0x00,0x00,0x81,0xC3,0xE7}, //动画帧15
{0xFF,0xCC,0x80,0x80,0x80,0xC0,0xE1,0xF3}, //动画帧16
{0xFF,0xE6,0xC0,0xC0,0xC0,0xE0,0xF0,0xF9}, //动画帧17
{0xFF,0x73,0x60,0x60,0x60,0x70,0x78,0xFC}, //动画帧18
{0xFF,0x39,0x30,0x30,0x30,0x38,0x3C,0x7E}, //动画帧19
{0xFF,0x9C,0x98,0x98,0x98,0x9C,0x1E,0x3F}, //动画帧20
{0xFF,0xCE,0xCC,0xCC,0xCC,0xCE,0x0F,0x1F}, //动画帧21
{0xFF,0x67,0x66,0x66,0x66,0x67,0x07,0x0F}, //动画帧22
{0xFF,0x33,0x33,0x33,0x33,0x33,0x03,0x87}, //动画帧23
{0xFF,0x99,0x99,0x99,0x99,0x99,0x81,0xC3}, //动画帧24
{0xFF,0xCC,0xCC,0xCC,0xCC,0xCC,0xC0,0xE1}, //动画帧25
{0xFF,0xE6,0xE6,0xE6,0xE6,0xE6,0xE0,0xF0}, //动画帧26
{0xFF,0xF3,0xF3,0xF3,0xF3,0xF3,0xF0,0xF8}, //动画帧27
{0xFF,0xF9,0xF9,0xF9,0xF9,0xF9,0xF8,0xFC}, //动画帧28
{0xFF,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFE}, //动画帧29
{0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFF} //动画帧30
};
void main()
{
EA = 1; //使能总中断
ENLED = 0; //使能U4,选择LED点阵
ADDR3 = 0;
TMOD = 0x01; //设置T0为模式1
TH0 = 0xFC; //为T0赋初值0xFC67,定时1ms
TL0 = 0x67;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
while (1);
}
/* 定时器0中断服务函数 */
void InterruptTimer0() interrupt 1
{
static unsigned char i = 0; //动态扫描的索引
static unsigned char tmr = 0; //250ms软件定时器
static unsigned char index = 0; //图片刷新索引
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
//以下代码完成LED点阵动态扫描刷新
P0 = 0xFF; //显示消隐
switch (i)
{
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=image[index][0]; break;
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=image[index][1]; break;
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=image[index][2]; break;
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=image[index][3]; break;
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=image[index][4]; break;
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i++; P0=image[index][5]; break;
case 6: ADDR2=1; ADDR1=1; ADDR0=0; i++; P0=image[index][6]; break;
case 7: ADDR2=1; ADDR1=1; ADDR0=1; i=0; P0=image[index][7]; break;
default: break;
}
//以下代码完成每250ms改变一帧图像
tmr++;
if (tmr >= 250) //达到250ms时改变一次图片索引
{
tmr = 0;
index++;
if (index >= 30) //图片索引达到30后归零
{
index = 0;
}
}
}
下载进到板子上瞧瞧,是不是有一种特别好的感觉。技术外行人看的很神秘,其实做出来会发现逻辑很简单,每250ms更改一张图片,每1ms在中断里刷新单张图片的某一行。
不管是上下移动还是左右移动,都是对一帧帧的图片的切换,这种切换带来的视觉效果就是一种动态的了。录像实际上就是快速的拍摄了一帧帧的图片,然后对这些图片的快速回放,把动画效果给显示出来。因为硬件设计的缘故,所以在写上下移动程序的时候,数组定义的元素比较少,但是实际上也得理解成是32张图片的切换显示,而并非是真正的“移动”。
7.6 练习题
1、掌握变量的作用域以及存储类别。
2、了解点阵的显示原理,理解点阵动画显示原理。
3、独立完成点阵显示I❤U向下移动的程序。
4、独立完成点阵显示I❤U向右移动的程序。
5、用点阵做一个9到0的倒计时牌显示。
6、尝试实现流水灯、数码管和点阵的同时显示。
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !