51单片机入门——8X8点阵LED_单片机8×8led点阵-程序员宅基地

技术标签: 51单片机  51单片机入门  嵌入式硬件  单片机  

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 张图片的切换显示,而并非是真正的“移动”。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_56646606/article/details/122980667

智能推荐

C# List集合 GroupBy分组_c# list groupby-程序员宅基地

文章浏览阅读5.4k次。var grpBalance = listBalance.GroupBy(m => new { m.MerChantId, m.Name}).Distinct().Select(t => new balInfo { MerChantId = t.Key.MerChantId, ..._c# list groupby

【c语言基础】学习笔记+记录_c定义4字节数组-程序员宅基地

文章浏览阅读896次,点赞2次,收藏3次。c语言基础知识记录_c定义4字节数组

Kafka常见面试题-程序员宅基地

文章浏览阅读589次,点赞9次,收藏6次。接收端:对接收到的数据进行备份,定期进行检查对执行失败的数据重新执行;选择手动提交offset,对执行失败的数据不提交offset。零拷贝技术:在cache中未查到数据,从磁盘获取加载到cache中后,不copy到Kafka进程中,而是由操作系统直接发送到网卡。每隔一段时间消费数据,将当前时间与数据产生时间进行对比,小于延迟时间的不提交,并重新进入等待。在消费者端对数据进行幂等校验,禁止一定时间内出现相同含义的数据;页面缓存技术:将数据缓存到系统的cache中,在刷新到磁盘当中。

redis启动失败-程序员宅基地

文章浏览阅读49次。【代码】redis启动失败。

SQL知识点--插入记录_sql插入记录-程序员宅基地

文章浏览阅读6.3k次,点赞14次,收藏16次。INSERT INTO 语句INSERT INTO 语句用于向表中插入新记录,INSERT INTO 语句可以有两种编写形式。 第一种形式无需指定要插入数据的列名,只需提供被插入的值即可: INSERT INTOtable_name(表名称) VALUES (value1,value2,value3,...); 第二种形式需要指定列名及被插入的值: INSERT INTOtable_name(表名称)(column1,column2,column3,...) VALUES (value..._sql插入记录

端口扫描工具nmap的常用参数_nmap常用的扫描参数-程序员宅基地

文章浏览阅读1.8k次。nmap自学_nmap常用的扫描参数

随便推点

遍历Stream并设置属性值_stream遍历赋值-程序员宅基地

文章浏览阅读1.2w次。// 初始化数据,设置评价等级 List<TblApplyForCleaning> list = (List<TblApplyForCleaning>) tblApplyForCleaningRepository.findAll(); list.stream().forEach(p -> { if (p.getXing() != null)..._stream遍历赋值

Matlab之图片拼接_matlab如何把四张图片合成-程序员宅基地

文章浏览阅读4.6k次,点赞8次,收藏42次。功能:可以自定义行列数拼接图片,拼接同时可以自定义对每张图片进行增(减)白边处理。1.使用Matlab建立.m文件,具体如何建立见文章:Matlab基础之.m文件创建及使用2.我写了两份代码可供参考代码一%图片合并,可以实现将一组图片变为统一规格尺寸(默认第一张图的尺寸)%可以自定义行列数拼合成一整张图片,若图片数不足行列乘积数则用等尺寸白色图片代替%可以设置为每一张图片增加或减少白..._matlab如何把四张图片合成

6-4 Reverse Linked List_write a nonrecursive procedure to reverse a singly-程序员宅基地

文章浏览阅读1k次。6-4 Reverse Linked List (20 分)Write a nonrecursive procedure to reverse a singly linked list in O(N) time using constant extra space.Format of functions:List Reverse( List L );where List is ..._write a nonrecursive procedure to reverse a singly linked list in o(n) time

深度学习 Pytorch Mnist手写数字识别_mnist_train-程序员宅基地

文章浏览阅读606次。mnist手写数字识别_mnist_train

05-黑盒测试用例_反测试用例用写吗-程序员宅基地

文章浏览阅读117次。等价划分法用例问题用例按照测试分类:功能function、界面ui、性能performance、安全security、接口interface测试项:必须是确定的,可写可不写,最好不写身份证号没有字母,数字和罗马数字Ⅹ测试项:一般只写一个测试目的(测试目的必须是明确的,一个反向的(无效的等价类)只要违反一个需求)注意单选复选按钮依赖用例:下游的用例依赖上游的用例(已经存在的用例),用例依赖可以跨越模块(A设计员可能会依赖B设计员的测试用例)测试步骤:表明操作对象和方式,数据测试数据:没有数_反测试用例用写吗

三角平方数-程序员宅基地

文章浏览阅读724次。题目:最近毛哥买来一本数论方面的书《数论概论》,这本书上定义了两个很有趣的数:三角数,平方数。但是书上出了一个难题:当三角数和平方数相等时,叫做三角平方数。问你第n个三角平方数是什么。毛哥想了很久都不会,你能帮他找出第n个三角平方数吗?输入多组数组,输入一个数n(1<=n<=25)输出输出有两个数,第n个三角平方数(分别输出是第几个三角数和第几个平方数,中间用空格隔开)输..._三角平方数

推荐文章

热门文章

相关标签