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

    技术2025-10-16  22

    那些年的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);//右移,选择下一行 } } } } }

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

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

    Processed: 0.011, SQL: 9