(读者可自行跳过)因为缘分,我认识了你——51,还记得第一次我们见面时的,我的紧张不知所措吗,还记得你用流水灯的悦动来安慰我吗,这些我都记得,致我们的那些年……
本人大一,某985电子信息专业在读,因为兴趣和爱好,第一次接触到51,也渐渐喜欢上了他,下面内容,欢迎各位大佬批评指正
这里采用的是某宝清翔51单片机开发板(说明一下,不是因为做广告,单纯只是说明自己的开发板,因为开发板设计不同,具体到某一模块的代码是有差异的,望大家不要存疑)
STC80C51芯片与STC公司另一款芯片STC89C52很相似
这里区分一下周期概念:
时钟周期:是时序的最小时间单位,所谓时序可以简单理解成时间顺序
时钟周期=1/时钟源频率
本文采用芯片晶振频率为11.0592MHz
机器周期:是单片机完成一个操作的最短时间,主要针对汇编语言而言,在汇编语言下程序的每一条语句执行所使用的时间都是机器
周期的整数倍,51单片机系列标准架构下一个机器周期是12个时钟周期,也就是12/11059200秒
这里采用的共阳极LED灯组,分别接主控芯片8个IO口,所谓共阳极,也就是说,如果IO口输出高电平,无电势差,LED小灯无电流不点亮;反之,如果输出低电平 ,有电势差,LED小灯有电流点亮
以下是一次性电亮八个小灯的程序
#include //包含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
#include
#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
#include
#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 //包含51头文件
#include //包含移位标准库函数头文件
#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 编者的开发经验十分有限,欢迎各位大佬有更好的方法批评指正!
清翔的开发板上,点阵屏是单拿出来的外接模块,底部有两片级联的74H595芯片。前面我们已经学会了流水灯,和数码管,8X8点阵屏和他们有相似的地方,每一个点也是一个LED灯,通过控制IO口输出电平形成电势差来控制亮灭。工作机理是遍利每一行,在改行时,控制点亮的点,通过动态扫描,实现一个汉字的显示。这里遍历每一行的顺序时固定的,因此我们只需要控制在该行时点了的点即可,这里可以借助汉字取模软件PCtoLCD2002来直接得到所需汉字的列选值,放在一个数组中。
#include
#include //循环右移头文件
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);//右移,选择下一行
}
}
}
}
}
写道这里,第一次认真写博客的小白同学,紧张万分,不知道会有什么反响,也不知道自己掌握的情况如何,热烈欢迎大家批评指正,我也将和各位一起努力成长,追寻我们的热爱!
那些年,我们一同走过,这些年,我想和诸位一同走~可否?