1.初识点阵LED
1.1.什么是点阵LED
LED点阵屏通过LED(发光二极管)组成,以灯珠亮灭来显示文字、图片、动画、视频等,是各部分组件都模块化的显示器件,通常由显示模块、控制系统及电源系统组成。LED点阵显示屏制作简单,安装方便,被广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等。
点阵LED显示屏作为一种现代电子媒体设备,具有灵活的的显示面积(可任意的分割和拼装)、高亮度、长寿命、数字化、实时性等特点,应用非常广泛。前边学了LED小灯和LED数码管后,学习LED点阵就要轻松多了。一个数码管是由 8 个LED组成,同理,一个 8X8 的点阵就是由 64 个LED组成。如下图:
其内部结构原理图,如下图:
1.2.点亮LED点阵
从上图可知,每一行的LED的阳极(阴极)连接在一起由左侧的 8 个引脚控制(例如:第一行的LED的阳极由第 9 号引脚控制),每一列的LED的阴极(阳极)连接在一起由上侧的 8 个引脚控制。
那么如果我们把 9 号引脚置高电平、13 号引脚置低电平的话,左上角的那个LED小灯就会被点亮。我们先在proteus中建立仿真图:
#include<reg52.h>
void main()
{
P0 = 0 ;
P2 = 0 ;
while(1)
{
P0 = ~0x01 ;
P2 = 0x01 ;
}
}
仿真结果如下:
我们可以看到,左上脚的LED被点亮了。从这里我们可以逐步发现点阵的控制原理了。我们前面讲了一个数码管就是 8 个 LED 小灯,一个点阵是 64 个 LED 小灯。同样的道理,我们还可以把一个点阵理解成是 8 个数码管。经过前面的学习已经掌握了 6 个数码管同时显示的方法,那 8 个数码管也应该轻轻松松了。
1.3.点阵显示图案
独立的 LED 小灯可以实现流水灯,数码管可以显示多位数字,那点阵 LED 就得来显示一点花样了。
我们要显示花样的时候,往往要先做出来一些小图形,这些小图形的数据要转换到我们的程序当中去,这个时候就需要取模软件。给大家介绍一款简单的取模软件,这种取模软件在网上都可以下载到,大家来了解一下如何使用,先看一下操作界面。
鼠标点一下“新建图形”,根据我们板子上的点阵,把宽度和高度分别改成 8,然后点确定。
点击左侧的“模拟动画”菜单,再点击“放大格点”选项,一直放大到最大,那我们就可以在我们的 8X8 的点阵图形中用鼠标填充黑点,就可以画图形了。
取模软件是把黑色取为 1,白色取为 0
大家可以看到 P0 口控制的是一行,所以用“横向取模”,如果控制的是一列,就要选“纵向取模”。选中“字节倒序”这个选项,是因为左边是低位 DB0,右边是高位 DB7,所以是字节倒序,其它两个选项大家自己了解,点确定后,选择“取模方式”这个菜单,点一下“C51 格式”后,在“点阵生成区”自动产生了 8 个字节的数据,这 8 个字节的数据就是取出来的“模”。
大家注意,虽然我们用了软件来取模,但是也得知道其原理是什么,在这个图片里,黑色的一个格子表示一位二进制的 1,白色的一个格子表示一位二进制的 0。第一个字节是 0x00,其实就是这个 8X8 图形的第一行,全黑就是 0xFF;第二个字节是 0x66,低位在左边,高位在右边,大家注意看,黑色的表示 1,白色的表示 0,就组成了 0x66 这个数值。同理其它的数据大家也就知道怎么来的了。
那么下面我们就用程序把这些数据依次送到点阵上去,看看运行效果如何。
#include<reg52.h>
unsigned char i = 0;
unsigned char image[] = {
0x00,0x66,0x99,0x81,0x81,0x42,0x24,0x18
};
void display()
{
switch(i)
{
case 0 : P0 = 0xfe ; P2 = image[i] ; i ++ ; break ;
case 1 : P0 = 0xfd ; P2 = image[i] ; i ++ ; break ;
case 2 : P0 = 0xfb ; P2 = image[i] ; i ++ ; break ;
case 3 : P0 = 0xf7 ; P2 = image[i] ; i ++ ; break ;
case 4 : P0 = 0xef ; P2 = image[i] ; i ++ ; break ;
case 5 : P0 = 0xdf ; P2 = image[i] ; i ++ ; break ;
case 6 : P0 = 0xbf ; P2 = image[i] ; i ++ ; break ;
case 7 : P0 = 0x7f ; P2 = image[i] ; i = 0 ; break ;
}
}
void main()
{
EA = 1 ;
TMOD = 0x01; //设置 T0 为模式 1
TH0 = 0xFC; //为 T0 赋初值 0xFC67,定时 1ms
TL0 = 0x67;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
while(1);
}
void InterruptTimer0() interrupt 1
{
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
display();
}
对于 8X8 的点阵来说,我们可以显示一些简单的图形,字符等。但大部分汉字通常来说要用到 16X16 个点,而 8X8 的点阵只能显示一些简单笔画的汉字,大家可以自己取模做出来试试看。使用大屏显示汉字的方法和小屏的方法是类似的,所需要做的只是按照相同的原理来扩展行数和列数而已。
2.点阵的动画显示
点阵的动画显示,说到底就是对多张图片分别进行取模,使用程序算法巧妙的切换图片,多张图片组合起来就成了一段动画了,我们所看到的动画片、游戏等等,它们的基本原理也都是这样的。
2.1.点阵的纵向移动
上一节我们学了如何在点阵上画一个❤形,有时候我们希望这些显示是动起来的,而不是静止的。对于点阵本身已经没有多少的知识点可以介绍了,主要就是编程算法来解决问题了。比如我们现在要让点阵显示一个 I ❤ U 的动画,首先我们要把这个图形用取模软件画出来看一下。
这张图片共有 24 行,每 8 行组成一张点阵图片,并且每向上移动一行就出现了一张新图片,一共组成了 16 张图片。
用一个变量 index 来代表每张图片的起始位置,每次从 index 起始向下数 8 行代表了当前的图片,250ms 改变一张图片,然后不停的动态刷新,这样图片就变成动画了。首先我们要对显示的图片进行横向取模,虽然这是 16 张图片,由于我们每一张图片都是和下一行连续的,所以实际的取模值只需要 24 个字节就可以完成,我们来看看程序。
#include<reg52.h>
unsigned char index = 0 , tmr = 0 ;
unsigned char i = 0;
unsigned char image[] = {
0x7E,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,
0x66,0x99,0x81,0x81,0x42,0x24,0x18,0x00,
0x66,0x66,0x66,0x66,0x66,0x3C,0x00,0x00
};
void display()
{
switch(i)
{
case 0 : P0 = 0xfe ; P2 = image[i + index] ; i ++ ; break ;
case 1 : P0 = 0xfd ; P2 = image[i + index] ; i ++ ; break ;
case 2 : P0 = 0xfb ; P2 = image[i + index] ; i ++ ; break ;
case 3 : P0 = 0xf7 ; P2 = image[i + index] ; i ++ ; break ;
case 4 : P0 = 0xef ; P2 = image[i + index] ; i ++ ; break ;
case 5 : P0 = 0xdf ; P2 = image[i + index] ; i ++ ; break ;
case 6 : P0 = 0xbf ; P2 = image[i + index] ; i ++ ; break ;
case 7 : P0 = 0x7f ; P2 = image[i + index] ; i = 0 ; break ;
}
}
void main()
{
EA = 1 ;
TMOD = 0x01; //设置 T0 为模式 1
TH0 = 0xFC; //为 T0 赋初值 0xFC67,定时 1ms
TL0 = 0x67;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
while(1);
}
void InterruptTimer0() interrupt 1
{
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
display();
tmr ++ ;
if(tmr >= 125)
{
tmr = 0 ;
index ++ ;
if(index == 16)
index = 0 ;
}
}
大家把这个程序下载到单片机上看看效果,一个 I ❤ U 一直往上走动的动画就出现了,现在还有哪位敢说我们工科同学不懂浪漫的?还需要用什么玫瑰花取悦女朋友吗?一点技术含量都没有,要玩就玩点高科技,呵呵。
2.2.点阵的横向移动
上下移动我们会了,那我们还想左右移动该如何操作呢?
方法一:最简单,就是把板子侧过来放,纵向取模就可以完成。
这里大家是不是有种头顶冒汗的感觉?我们要做好技术,但是不能沉溺于技术。技术是我们的工具,我们在做开发的时候除了用好这个工具外,也得多拓展自己解决问题的思路,要慢慢培养自己的多角度思维方式。
那把板子正过来,左右移动就完不成了吗?当然不是。大家慢慢的学多了就会培养了一种感觉,就是一旦硬件设计好了,我们要完成一种功能,大脑就可以直接思考出来能否完成这个功能,这个在我们进行电路设计的时候最为重要。我们在开发产品的时候,首先是设计电路,设计电路的时候,工程师就要在大脑中通过思维来验证板子硬件和程序能否完成我们想要的功能,一旦硬件做好了,做好板子回来剩下的就是靠编程来完成了。只要是硬件逻辑上没问题,功能上软件肯定可以实现。
当然了,我们在进行硬件电路设计的时候,也得充分考虑软件编程的方便性。因为我们的程序是用 P0 来控制点阵的整行,所以对于我们这样的电路设计,上下移动程序是比较好编写的。那如果我们设计电路的时候知道我们的图形要左右移动,那我们设计电路画板子的时候就要尽可能的把点阵横过来放,有利于我们编程方便,减少软件工作量。
方法二:和纵向取模一样,将横向的共阴数码管看成是纵向的共阳数码管。
我们先设计如图的图案。
#include<reg52.h>
unsigned char index = 0 , tmr = 0 ;
unsigned char i = 0;
unsigned char image[] = {
0x81,0x81,0xFF,0xFF,0x81,0x81,0x00,0x70,
0x88,0x84,0x82,0x41,0x41,0x82,0x84,0x88,
0x70,0x00,0xFE,0xFF,0x03,0x03,0xFF,0xFE,
};
void display()
{
switch(i)
{
case 0 : P2 = ~0xfe ; P0 = ~image[i + index] ; i ++ ; break ;
case 1 : P2 = ~0xfd ; P0 = ~image[i + index] ; i ++ ; break ;
case 2 : P2 = ~0xfb ; P0 = ~image[i + index] ; i ++ ; break ;
case 3 : P2 = ~0xf7 ; P0 = ~image[i + index] ; i ++ ; break ;
case 4 : P2 = ~0xef ; P0 = ~image[i + index] ; i ++ ; break ;
case 5 : P2 = ~0xdf ; P0 = ~image[i + index] ; i ++ ; break ;
case 6 : P2 = ~0xbf ; P0 = ~image[i + index] ; i ++ ; break ;
case 7 : P2 = ~0x7f ; P0 = ~image[i + index] ; i = 0 ; break ;
}
}
void main()
{
EA = 1 ;
TMOD = 0x01; //设置 T0 为模式 1
TH0 = 0xFC; //为 T0 赋初值 0xFC67,定时 1ms
TL0 = 0x67;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
while(1);
}
void InterruptTimer0() interrupt 1
{
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
display();
tmr ++ ;
if(tmr >= 125)
{
tmr = 0 ;
index ++ ;
if(index == 16)
index = 0 ;
}
}
方法三:利用二维数组来实现,算法基本上和上下移动相似。
那么下面我们要进行横向做 I ❤ U 的动画了,先把我们需要的图片画出来,再逐一取模,和上一张图片类似的是,我们这个图形共有 30 张图片,通过程序每 125ms 改变一张图片,就可以做出来动画效果了。但是不同的是,我们这个是要横向移动,横向移动的图片切换时的字模数据不是连续的,所以这次我们要对 30 张图片分别取模。
最上面的图形是横向连在一起的效果,而实际上我们要把它分解为 30 个帧,
每帧图片单独取模,取出来都是 8 个字节的数据,一共就是 30X8 个数据,我们用一个二维数组来存储它们。
#include<reg52.h>
unsigned char index = 0 , tmr = 0 ;
unsigned char i = 0;
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 display()
{
switch(i)
{
case 0: P0 = 0xfe ; i++; P2=image[index][0]; break;
case 1: P0 = 0xfd ; i++; P2=image[index][1]; break;
case 2: P0 = 0xfb ; i++; P2=image[index][2]; break;
case 3: P0 = 0xf7 ; i++; P2=image[index][3]; break;
case 4: P2 = 0xef ; i++; P2=image[index][4]; break;
case 5: P2 = 0xdf ; i++; P2=image[index][5]; break;
case 6: P2 = 0xbf ; i++; P2=image[index][6]; break;
case 7: P2 = 0x7f ; i=0; P2=image[index][7]; break;
default: break;
}
}
void main()
{
EA = 1 ;
TMOD = 0x01; //设置 T0 为模式 1
TH0 = 0xFC; //为 T0 赋初值 0xFC67,定时 1ms
TL0 = 0x67;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
while(1);
}
void InterruptTimer0() interrupt 1
{
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
display();
tmr ++ ;
if(tmr >= 125)
{
tmr = 0 ;
index ++ ;
if(index == 30)
index = 0 ;
}
}
下载进到板子上瞧瞧,是不是有一种帅到掉渣的感觉呢。技术这东西,外行人看的是很神秘的,其实我们做出来会发现,也就是那么回事而已,每 125ms 更改一张图片,每 1ms在定时器中断里刷新单张图片的某一行。
不管是上下移动还是左右移动,大家要建立一种概念,就是我们是对一帧帧的图片的切换,这种切换带给我们的视觉效果就是一种动态的了。比如我们的 DV 拍摄动画,实际上就是快速的拍摄了一帧帧的图片,然后对这些图片的快速回放,把动画效果给显示了出来。因为我们硬件设计的缘故,所以在写上下移动程序的时候,数组定义的元素比较少,但是实际上大家也得理解成是 30 张图片的切换显示,而并非是真正的“移动”。