那些年的51单片机系列---分模块学习总结(一)流水灯&数码管(鬼影消除)&8X8点阵

那些年的51单片机系列—分模块学习总结(一)流水灯&数码管(鬼影消除)&8X8点阵

写在前面

(读者可自行跳过)因为缘分,我认识了你——51,还记得第一次我们见面时的,我的紧张不知所措吗,还记得你用流水灯的悦动来安慰我吗,这些我都记得,致我们的那些年……
本人大一,某985电子信息专业在读,因为兴趣和爱好,第一次接触到51,也渐渐喜欢上了他,下面内容,欢迎各位大佬批评指正

主控芯片

这里采用的是某宝清翔51单片机开发板(说明一下,不是因为做广告,单纯只是说明自己的开发板,因为开发板设计不同,具体到某一模块的代码是有差异的,望大家不要存疑)
STC80C51芯片与STC公司另一款芯片STC89C52很相似
在这里插入图片描述
这里区分一下周期概念:
时钟周期:是时序的最小时间单位,所谓时序可以简单理解成时间顺序
时钟周期=1/时钟源频率
本文采用芯片晶振频率为11.0592MHz
机器周期:是单片机完成一个操作的最短时间,主要针对汇编语言而言,在汇编语言下程序的每一条语句执行所使用的时间都是机器
周期的整数倍,51单片机系列标准架构下一个机器周期是12个时钟周期,也就是12/11059200秒

流水灯

在这里插入图片描述
这里采用的共阳极LED灯组,分别接主控芯片8个IO口,所谓共阳极,也就是说,如果IO口输出高电平,无电势差,LED小灯无电流不点亮;反之,如果输出低电平 ,有电势差,LED小灯有电流点亮

以下是一次性电亮八个小灯的程序

#include <reg52.h>//包含51头文件

unsigned int i;//0~65535

void main()//main函数自身会循环
{
       while(1)//大循环
       {
              P1= 0;   //点亮P1口8个LED
              i= 65535;
              while(i--);//软件延时
              P1= 0xff;//1111 1111 熄灭P1口8个LED
              i= 65535;
              while(i--);//软件延时    
       }      
}

下面是自己做的一个花式流水灯效果
视频效果可看添加链接描述
https://www.bilibili.com/video/BV1ok4y1z7ti

#include <reg52.h>
#include <intrins.h>

#define uchar unsigned char
#define uint unsigned int 

void delay(uint z)
{
	uint x,y;
	for(x=z;x>0;x--)
		for(y=114;y>0;y--);	
}

void main()
{
	uchar temp1,temp2,temp3,temp4,Temp;
	uint t=200,i,a2,a,b,c;
	uchar A[]={0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80};
	uchar B[]={0xff,0xe7,0xc3,0x81,0x00};
	uchar C[]={0xfe,0xfc,0xf8,0xf0,0xf0,0xf1,0xf3,0xf7};
	
	//1
	temp1=0xaa;
	for(i=16;i>0;i--)
	{
		P1=temp1;
		delay(t);
		temp1=~temp1;
		P1=temp1;
		delay(t);
		t=t-10;
	}
	for(i=20;i>0;i--)
	{
		t=40;
		P1=temp1;
		delay(t);
		temp1=~temp1;
		P1=temp1;
		delay(t);	
	}
	temp1=0x00;
	P1=temp1;
	delay(1000);

	//2
	for(a2=0;a2<4;a2++)
	{
	temp2=C[a2];
	P1=temp2;
	delay(250);
	temp2=_cror_(temp2,a2+1);
	P1=temp2;
	delay(250);
	}
	for(a2=0;a2<4;a2++)
	{
	temp2=C[a2+4];
	P1=temp2;
	delay(250);
	temp2=_crol_(temp2,4-a2);
	P1=temp2;
	delay(250);
	}

	//3		
		//左加
		c=1;
		Temp=0xff;
		temp3=0x01;
		for(b=8;b>0;b--)
		{
			for(a=0;a<b;)
			{
			Temp=Temp^temp3;
			P1=Temp;
			delay(100);
			a++;
			if(a!=b)Temp=Temp|temp3;
			if(a!=b)temp3=_crol_(temp3,1);
			else temp3=_crol_(temp3,c); 
			}
			c++;
		}
		delay(100);
		
		//右减
		c=1;
		Temp=0x00;
		temp3=0x80;
		for(b=8;b>0;b--)
		{
			for(a=0;a<b;)
			{
			Temp=Temp|temp3;
			P1=Temp;
			delay(100);
			a++;
			if(a!=b)Temp=Temp^temp3;
			if(a!=b)temp3=_cror_(temp3,1);
			else temp3=_cror_(temp3,c); 
			}
			c++;
		}
		delay(100);

		//右加
		c=1;
		Temp=0xff;
		temp3=0x80;
		for(b=8;b>0;b--)
		{
			for(a=0;a<b;)
			{
			Temp=Temp^temp3;
			P1=Temp;
			delay(100);
			a++;
			if(a!=b)Temp=Temp|temp3;
			if(a!=b)temp3=_cror_(temp3,1);
			else temp3=_cror_(temp3,c); 
			}
			c++;
		}
		delay(100);
		
		//左减
		c=1;
		Temp=0x00;
		temp3=0x01;
		for(b=8;b>0;b--)
		{
			for(a=0;a<b;)
			{
			Temp=Temp|temp3;
			P1=Temp;
			delay(100);
			a++;
			if(a!=b)Temp=Temp^temp3;
			if(a!=b)temp3=_crol_(temp3,1);
			else temp3=_crol_(temp3,c); 
			}
			c++;
		}
		delay(100);

	//4

	for(a=0;a<7;a++)
	{
	   	temp4=A[a];
	   	for(b=8-a;b>0;b--)
		{
		P1=temp4;
		temp4=_crol_(temp4,1);
		delay(100);
		}
		temp4=_cror_(temp4,1);
		for(b=8-a;b>0;b--)
		{
		P1=temp4;
		temp4=_cror_(temp4,1);
		delay(100);
		}
	}
	temp4=0x00;
	P1=temp4;
	delay(100);
	for(a=3;a>0;a--)
	{
		for(b=0;b<5;b++)
		{
			temp4=B[b];
			P1=temp4;
			delay(100);
		}
	}
}

说明:由于人眼具有视觉暂留最小时间,当LED灯闪烁小于0.2s时,人眼几乎无法分辨,这里用到了delay()毫秒级延迟函数,所根据的即上述周期的概念,用空语句来占据时间,达到延迟效果。而_crol_(),cror()语句为循环左右移,与<<和>>不同的是,循环移位是八位数循环,边位数据不会被挤掉

数码管

在这里插入图片描述
简单的说,数码管本质也是LED灯,由八个LED灯组成的段构成,最后一个段即每一位上的点,清翔51开发板上用的共阴极数码管,与流水灯共阳极类似,读者可自行理解。看到这里,相信大家都会发问,流水灯八个已经用了八个IO口了,数码管这么的位,每一个都有八个LED灯,那需要多少IO口啊,8x8=64?芯片引脚够用吗?为了解决这个问题,方法有很多种,而常用的是利用锁存器,清翔开发板上用的两片级联的74H573锁存器。
在这里插入图片描述
74H573锁存器的利用大大节省了IO口的使用,只使用8个IO口便可达到控制8位数码管的作用,使得其他IO口可以做其他用途使用。它的工作形式可以简单描述为,主控芯片依次先后用8个IO口发送位选值和段选值,位选值先用来控制哪一位的数码管,而段选值用来控制这位数码管具体显示哪一个数字。

静态显示

到这里我们就可以实现具体的一位数码管的静态静态显示:

#include <reg52.h>
#include <intrins.h>

#define uint unsigned int
#define uchar unsigned char

sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选

void main()//main函数自身会循环
{
	WE = 1;//打开位选锁存器
	P0 = 0XFE; //1111 1110 选通第一位数码管
	WE = 0;//锁存位选数据

	DU = 1;//打开段选锁存器
	P0 = 0X06;//0000 0110 显示“1”
	DU = 0;//锁存段选数据
	
	while(1)
	{
	}
}  

动态扫描

静态显示效率太低了,每次上电只能显示一位数字,而想要多为数码管,多个数字扫描便需要动态显示。动态显示原理是,依据人眼的视觉暂留效果,机器运行时间极短,在这种条件下,先后控制不同位数码管显示数字,使得人眼看到的效果是显示了许多位,而本质,每次只显示一位。这里因为只用8个IO口,涉及位选和段选的切换,会造成改变选值时,在极短的时间内,上一个数据会转送进来并显示,使得数码管看上去有鬼影效果。消除鬼影的方法很多,很多博主都有介绍,而学习了几个月的51,自己也并没有完全掌握,鬼影时而也是存在的,可见微观世界的奇妙就像爱情一样,不是我们随随便便都能猜透彼此的心思,你可能想要的不要显鬼影,而她确如鬼影一般时时跟随着你,不离不弃,生死相依,人类宏观世界的奇妙,在微观世界同样存在,微观与宏观之间也存在着千丝万缕的联系,通过一行行美丽的代码文字而表达(纯属编者瞎扯…………)
下面先附上动态扫描的代码:

#include <reg52.h>//包含51头文件
#include <intrins.h>//包含移位标准库函数头文件

#define uint unsigned int
#define uchar unsigned char

sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选

//共阴数码管段选表0-9
uchar  code tabel[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};

void delay(uint z)
{
	uint x,y;
	for(x = z; x > 0; x--)
		for(y = 114; y > 0 ; y--); 		
} 

void display(uchar i)
{
	uchar bai, shi, ge;
	bai = i / 100; //236 / 100  = 2
	shi = i % 100 / 10;	//236 % 100 / 10 = 3
	ge  = i % 10;//236 % 10 =6
	
	//第一位数码管  		
	P0 = 0XFF;//清除断码
	WE = 1;//打开位选锁存器
	P0 = 0XFE; //1111 1110
	WE = 0;//锁存位选数据
	
	DU = 1;//打开段选锁存器
	P0 = tabel[bai];//
	DU = 0;//锁存段选数据
	delay(5);

	//第二位数码管
	P0 = 0XFF;//清除断码
	WE = 1;//打开位选锁存器
	P0 = 0XFD; //1111 1101
	WE = 0;//锁存位选数据
	
	DU = 1;//打开段选锁存器
	P0 = tabel[shi];//
	DU = 0;//锁存段选数据
	delay(5);

	//第三位数码管
	P0 = 0XFF;//清除断码
	WE = 1;//打开位选锁存器
	P0 = 0XFB; //1111 1011
	WE = 0;//锁存位选数据
	
	DU = 1;//打开段选锁存器
	P0 = tabel[ge];//
	DU = 0;//锁存段选数据
	delay(5);
}

void main()//main函数自身会循环
{	
	while(1)
	{
		display(236); //数码管显示函数
	}	
}  

聪明的你会惊奇的发现,咦,这个段选表是什么东东捏?原来,在控制每一位数码管显示具体数字时,数字的样式对应数码管的需要点亮LED的结构时对应的,这种对应关系映射到用数组表达,每次需要显示一个数字的时候,让主控芯片输出对应数组的值,是不是效率就高了很多,代码理解起来也更方便,移植性也更强,当然我们也可以将位选也写成数组会更加方便。
附上完整的表格:

uchar code SMGduan[]={//共阴极数码管段选表
//0	  1    2    3    4    5    6    7    8	  9
0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f
};
uchar code SMGwei[8]={//共阴极数码管位选表
//1    2    3    4    5    6    7    8
0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};

鬼影消除

常用的方法是:
①P0 = 0XFF;在位选之前使用,达到消除鬼影的作用。
②更改延时,减少延时的时间或者中断里面的时间。
PS 编者的开发经验十分有限,欢迎各位大佬有更好的方法批评指正!

8X8点阵

在这里插入图片描述在这里插入图片描述
清翔的开发板上,点阵屏是单拿出来的外接模块,底部有两片级联的74H595芯片。前面我们已经学会了流水灯,和数码管,8X8点阵屏和他们有相似的地方,每一个点也是一个LED灯,通过控制IO口输出电平形成电势差来控制亮灭。工作机理是遍利每一行,在改行时,控制点亮的点,通过动态扫描,实现一个汉字的显示。这里遍历每一行的顺序时固定的,因此我们只需要控制在该行时点了的点即可,这里可以借助汉字取模软件PCtoLCD2002来直接得到所需汉字的列选值,放在一个数组中。

#include <reg52.h>
#include <intrins.h> //循环右移头文件
sbit DIO = P3^4;  //串行数据口
sbit S_CLK = P3^5;//移位寄存器时钟
sbit R_CLK = P3^6;//输出锁存器时钟
/*点阵字形码*/
unsigned char code tabel[2][8]={

0xE0,0xEE,0x01,0x6D,0x01,0x6D,0x01,0xEF,//电
0xE7,0xF7,0xF7,0xF7,0x80,0xF7,0xFB,0xC3//子
};
/*595发送一字节*/
void Send_Byte(unsigned char dat)
{
	unsigned char i; //循环次数变量
	S_CLK = 0;//拉低移位寄存器时钟
	R_CLK = 0;//拉低输出锁存器时钟
	for(i=0; i<8; i++) //循环8次
	{
		if(dat & 0x01)//发送1
			DIO = 1;
			else	  //发送0
			DIO = 0;
		dat >>= 1;//数据右移
		S_CLK = 1;//拉高移位寄存器时钟,数据移位
		S_CLK = 0;//拉低移位寄存器时钟
	}	
}
void main()
{
	unsigned char j, k, ROW;//j发送8列和8行字形码,k字符数量,ROW行值
	unsigned int z;	//动态扫描延时变量
	while(1)
	{
		for(k=0; k<2; k++)//k 需要显示的字符数量
		{
			for(z=0; z<1000; z++)//z刷新次数
			{
				ROW = 0x80;//行选初值
				for(j=0; j<8; j++) //循环8次发送行和列值
				{
					Send_Byte(tabel[k][j]);//发送列选值
					Send_Byte(ROW);	//发送行选值
					R_CLK = 1; //拉高输出锁存器,把移位寄存器中数据输出
					R_CLK = 0; //拉低输出锁存器
					ROW = _cror_(ROW, 1);//右移,选择下一行	
				}
			}
		}
	}
}

写道这里,第一次认真写博客的小白同学,紧张万分,不知道会有什么反响,也不知道自己掌握的情况如何,热烈欢迎大家批评指正,我也将和各位一起努力成长,追寻我们的热爱!

那些年,我们一同走过,这些年,我想和诸位一同走~可否?

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值