51单片机课程设计——led点阵广告牌程序设计
2020-05-08 来源:eefocus
上学期期末的课设题目是led点阵广告牌,当时的要求如下:
(1)能够显示不同字符的LED点阵广告牌;
(2)按键切换不同的显示效果(如闪烁,静止,平移等);
(3)按键切换不同的显示内容;
(4)能够显示图形或自定义字符;
(5)其他功能(创新部分);
(6)系统调试、分析、总结与功能实现。
当时用的是我用是的普中科技的STC90C51RD+的单片机,不过只要是51单片机,换哪个51内核的芯片都可以,只要电路和程序匹配就可以。
我用的那款普中科技的51单片机可以直接连线决定线路的布置,不需要自己去重新焊一个电路板。
然后我根据任务要求连接了电路,写了对应的程序,还有proteus仿真。
另外,觉得有趣可以点个赞;有什么有趣的想法可以评论一下,我感兴趣的话会做一下。
一、设计思路
首先是P1连接8个独立按键,P3.4,P3.5,P3.5三个I/O口通过模拟SPI(51单片机没有硬件的SPI总线控制器)控制led点阵。
下面是包含头文件,定义独立键盘,选用模拟SPI的三个I/O口的程序内容,其中也包括重定义函数变量的内容,可以有效地减少编写程序时的负担。
#include #include #define GPIO_KEY P1 //独立键盘用P1口 //--重定义函数变量--// #define uchar unsigned char #define uint unsigned int #define ulong unsigned long //--定义SPI要使用的 IO--// sbit MOSIO = P3^4; sbit R_CLK = P3^5; sbit S_CLK = P3^6; 之后设计显示在led点阵上的文字,符号。(当时设计的是我和组员的名字还有一些自定义的符号)。 要实现的效果是:打开电源后按下K1可以一堆汉字会从上而下移动,按下K2汉字会闪烁显示(闪烁显示就是显示一个字零点几秒钟,然后先后显示其他字),按下K3是我自定义的那些字符闪烁显示,然后进入每个功能后都可以按下K8退出这个功能,之后可以按下K1,K2或K3进入这三个功能。 二、通过I/O口模拟SPI发送数据 (1)传输数据函数与对应模块的仿真 以下是普中科技单片机自带的通过I/O口模拟SPI发送数据的函数,因为有配套的字模生成软件,所以对这个部分我就不写太多东西了,有想要字模软件的可以在文末的百度网盘链接自己下载。 //通过74HC595发送四个字节的数据 void HC595SendData(uchar BT3, uchar BT2,uchar BT1,uchar BT0) { uchar i; //--发送第一个字节--// for(i=0;i<8;i++) { MOSIO = BT3 >> 7 ; //从高位到低位 BT3 <<= 1; S_CLK = 0; S_CLK = 1; } //--发送第二个字节--// for(i=0;i<8;i++) { MOSIO = BT2 >>7; //从高位到低位 BT2 <<= 1; S_CLK = 0; S_CLK = 1; } //--发送第三个字节--// for(i=0;i<8;i++) { MOSIO = BT1 >> 7; //从高位到低位 BT1 <<= 1; S_CLK = 0; S_CLK = 1; } //--发送第四个字节--// for(i=0;i<8;i++) { MOSIO = BT0 >> 7; //从高位到低位 BT0 <<= 1; S_CLK = 0; S_CLK = 1; } //--输出--// R_CLK = 0; //set dataline low R_CLK = 1; //片选 R_CLK = 0; //set dataline low } 这个部分使用了4个74HC595,下面是对应的仿真图片,里面RX8是排阻,可以不要,我这个图片里有是因为普中科技给的原理图里有这个排阻,我做仿真的时候直接照猫画虎画上去了,其实完全不需要,普中的板子上有这个是因为要把各个模块分开,连接的时候用排线连接就可以,POS和NEG可以理解为16 * 16LED点阵的行和列。 LED点阵要求是16 * 16的,需要使用4个8 * 8的LED点阵组合起来,在proteus中的制作方法在 这里,我拼接16 * 16LED点阵是按照这个做的,之后按照普中给的原理图写引脚的网络标号,在proteus对应着标了一遍(注意:这个图的网络标号是错的),然后仿真出来的结果有问题,我重画了很多次都有问题,可能是原理图有问题,也可能是别的原因,使用普中科技的板子的朋友可以注意一下,我按照这个原理图画仿真怎么画也不对,最后干脆16个行分别标上POS1到POS16,16个列标上NEG1到NEG16结果对了。 下面是最终可以使用的点阵仿真图: 下面是原理图(照着画网络标号有问题,其他的没有问题): 下面是照着原理图画的仿真(画了两次都不能用,出现的图案与预期不符): (2)要传输的数据 传输的数据需要选择一些汉字,字符或者自定义符号。 以下为一些姓氏和自定义的字符对应的数组,tab0是用来确定显示位置的,下面会提到。 //点阵显示数组 uchar code tab0[] = {0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00}; //--张--// uchar code tab1[] = { 128,1,191,49,176,49,176,25,176,13,190,7,134, 1,230,255,134,7,190,13,176,13,176,25,176,49,176,103,158,195,140,1}; //--王--// uchar code tab1[] = { 0,0,252,31,128,0,128,0,128,0,128,0,128,0,248, 15,128,0,128,0,128,0,128,0,128,0,254,63,0,0,0,0}; //--李--// uchar code tab2[] = { 128,0,128,0,254,63,160,2,144,4,136,8,6,48,240, 3,0,1,128,0,254,63,128,0,128,0,224,0,0,0,0,0}; //--任--// uchar code tab3[] = { 16,0,16,28,208,3,8,2,8,2,12,2,10,2,232,63,8,2, 8,2,8,2,8,2,8,2,200,31,0,0,0,0}; //--徐-// uchar code tab4[] = { 16,2,16,2,8,5,132,8,82,16,176,47,8,2,12,2,202, 31,8,2,72,18,72,34,40,34,136,3,0,0,0,0}; //--杨--// uchar code tab5[] = { 24,0,152,63,24,24,24,12,127,6,24,3,156,255,60, 219,126,219,126,219,155,217,152,205,216,204,120,198,24,123,152,49}; //--桃心--// uchar code xin[] = { 0,0,28,56,252,63,254,127,255,255,207,243,135, 225,7,224,15,240,30,120,60,60,120,30,240,15,224,7,192,3,128,1}; //--五角星--// uchar code char1[] = { 128,1,128,1,192,3,192,3,192,3,96,6,127,254,6, 96,28,56,48,12,48,12,152,27,248,30,56,28,12,48,0,0}; //--圆--// uchar code char2[] = { 0,0,224,7,120,30,28,56,28,56,14,112,14,112,14, 112,14,112,14,112,28,56,28,56,120,30,224,7,0,0,0,0}; //--三角--// uchar code char3[] = { 0,0,128,1,128,1,192,3,192,3,96,6,96,6,48,12,48, 12,24,24,24,24,12,48,12,48,254,127,0,0,0,0}; //--菱形--// uchar code char4[] = { 128,0,64,1,32,2,16,4,8,8,4,16,2,32,1,64,2,32, 4,16,8,8,16,4,32,2,64,1,128,0,0,0}; //--箭头--// uchar code char5[] = { 0,0,0,0,0,0,0,2,0,4,0,8,0,16,0,32,126,64,0, 32,0,16,0,8,0,4,0,2,0,0,0,0}; 之后会将要显示的数组在混合起来成为一个新的指针数组,这个数组是用来直接在点阵显示的函数中使用的,*p的指针数组中多余很多是被我删掉的名字,只留下了姓。 uchar *p[] = {tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, tab9, tab10, tab11, tab12, tab13, tab14}; uchar *c[] = {char1, char2, char3, char4,char5}; 三、检测独立按键 主函数中首先是while(1),无限循环,只要没有检测到键值(即独立按键中有任意键被按下),就一直循环,并且执行清屏的程序(即调用标题二中的传输数据功能,分别传输0xff,0xff,0,0四个数据)如果检测到键值就进入对应的功能中,进入每个功能对应的函数。 以下是主函数内容: void main(void) { while(1) { HC595SendData(0xff,0xff,0,0); //清屏 keyNum=Key_Scan(); //读取键值 switch (keyNum) { case(0xFE) : //返回按键K1的数据 translation();//文字从上到下平移 break; case(0xFD) : //返回按键K2的数据 twinkle1(); //文字闪烁 break; case(0xFB) : //返回按键K3的数据 twinkle2(); //符号闪烁 break; // case(0xF7) : //返回按键K4的数据 // ; // break; // case(0xEF) : //返回按键K5的数据 // ; // break; // case(0xDF) : //返回按键K6的数据 // ; // break; // case(0xBF) : //返回按键K7的数据 // ; // break; case(0x7F) : //返回按键K8的数据 HC595SendData(0xff,0xff,0,0); //清屏 break; default: break; } } } 读取键值 前面有提到定义P1为独立按键,即程序中的GPIO_KEY,当独立按键按下时GPIO_KEY != 0xFF,这时消除抖动(即延时十毫秒)再检测一次,如果检测为按下按键则将键值保存到keyValue中,之后松开按键或500ms后仍未松开按键,都会将读取到的键值返回。 unsigned char Key_Scan() { unsigned char keyValue = 0 , i; //保存键值 //--检测按键1--// if (GPIO_KEY != 0xFF) //检测按键K1是否按下 { Delay10ms(1); //消除抖动 if (GPIO_KEY != 0xFF) //再次检测按键是否按下 { keyValue = GPIO_KEY; i = 0; while ((i<50) && (GPIO_KEY != 0xFF)) //检测按键是否松开 { Delay10ms(1); i++; } } } return keyValue; //将读取到键值的值返回 } 四、具体功能 (1)文字自上而下移动 K1按键对应的是文字自上而下移动的功能,以下为对应的程序,其中内嵌的第三个循环for(k = 0; k < 16; k++)中k从0到15遍历,对应LED点阵的1到16行,内嵌的第二个循环for(ms = 10; ms > 0; ms–)决定显示同一个内容的时间,内嵌的第一个循环为while(keyNum!=0x7F),只要keyNum!=0x7F一直成立,就无限循环。 i.如何实现随时按下K8退出该功能,重新回到主函数 在最小的循环中完成内嵌一个检测键值的程序keyNum=Key_Scan();然后判断是否为0x7F(即按下K8时的键值),若是则退出该循环,回到第二个循环,第二个循环中也有判断键值是否为K8的程序,同理,若是,回到第一个循环,while(keyNum!=0x7F)同样会判断键值是否为0x7F,若不是则退出循环,回到主函数。 ii.如何实现文字从上而下移动 刚进入这个功能还未进入循环时,赋值j=0(如果这里不赋值j=0也可以,只是下次进入这个功能会接着这次结束时显示的图像继续向下平移),在内嵌的第三个循环,也就是最小的的那个循环中给LED点阵发送数据,且是一次发送一行数据,循环16次,每次k+1即可显示全整个LED点阵,k加到16时退出最小的循环,第二个循环for(ms = 10; ms > 0; ms–)决定进入最小循环的次数即显示的时间,显示完规定的时间后,退出第二个循环,回到第三个循环,j+1,之后再进入最小循环时发送的前两位数据对应的指针数组中的位置会各自加2j,即显示的文字或字符向上移动一位,最下方空缺出来的一行会由下一个文字或字符的第一行代替,之后同理,则形成了文字向上移动的效果,一个字需要16行,从0开始,当j=15要显示的文字或字符数量时,再令j=0,重新从第一个字开始显示。 以下是文字移动的程序: //文字由上而下平移 void translation() { j=0; while(keyNum!=0x7F) { for(ms = 10; ms > 0; ms--) //移动定格时间设置 { for(k = 0; k < 16; k++) //显示一个字 { HC595SendData(~(*(p[0] + 2*(k+j) + 1)),~(*(p[0] + 2*(k+j) )), tab0[2*k],tab0[2*k + 1]); //因为字模软件取的数组是高电平有效,所以列要取反 keyNum=Key_Scan(); if(keyNum==0x7F) break; } HC595SendData(0xff,0xff,0,0); //清屏 if(keyNum==0x7F) break; } j++; if(j == (14*15) ) { j = 0; } } } 以下是文字移动的效果(这时第一个字已经消失了近一半,下一个字也显示出一小半,分别是“王”字和“李”字): (2)文字闪烁显示 i.如何实现随时按下K8退出该功能,重新回到主函数 与文字从上而下移动的功能同理,只不过文字闪烁功能有四个循环,比文字移动多一个,同样是最小循环中检测键值,若检测到keyNum==0x7F,则逐级退出,最后返回主函数。 ii.如何实现文字闪烁显示 与文字移动有些类似的地方,在最小的循环中发送数据从LED点阵第一行显示到最后一行,上一级循环决定最小循环一共运行几次,即显示一个字的时间,再上一级循环中每次i+1决定显示下一个文字,当i=14时回到最外层循环,之后回到刚才的循环重新从第一个文字开始显示。 第二个循环中有多少要显示的文字,就循环多少次,例如我原先共设计了14个文字,则 for(i = 0; i < 14; i++) 中i<14。 以下是文字闪烁的程序: //文字闪烁 void twinkle1() { while(keyNum!=0x7F) { for(i = 0; i < 14; i++) //总共14个字 { for(ms = 50; ms > 0; ms--) //显示50次,即肉眼可识别的停留时间 { for(k = 0; k < 16; k++) //显示一个字 { //--因为字模软件取的数组是高电平有效,所以列要取反--// HC595SendData(~(*(p[i] + 2*k + 1)),~(*(p[i] + 2*k )), tab0[2*k],tab0[2*k + 1]); keyNum=Key_Scan(); if(keyNum==0x7F) break; } HC595SendData(0xff,0xff,0,0); //清屏 if(keyNum==0x7F) break; } if(keyNum==0x7F) break; } } } 以下是文字闪烁的效果,一瞬间只显示一个文字: (3)符号闪烁显示 其原理与文字闪烁显示完全相同,只不过这个程序里包含的指针数组里的全是自定义的符号。 以下是符号闪烁的程序: //符号闪烁 void twinkle2() { while(keyNum!=0x7F) { for(i = 0; i <5; i++) //总共5个字 { for(ms = 50; ms > 0; ms--) //显示50次,即肉眼可识别的停留时间 { for(k = 0; k < 16; k++) //显示一个字 { //--因为字模软件取的数组是高电平有效,所以列要取反--// HC595SendData(~(*(c[i] + 2*k + 1)),~(*(c[i] + 2*k )), tab0[2*k],tab0[2*k + 1]); keyNum=Key_Scan(); if(keyNum==0x7F) break; } HC595SendData(0xff,0xff,0,0); //清屏 if(keyNum==0x7F) break; } if(keyNum==0x7F) break; } } } 以下是符号闪烁的效果,一瞬间只显示一个自定义符号: (4)创新部分 做这个程序的时候快要期末考试了,就没有费心思去做创新部分,但其实很简单,例如,可以把符号也弄成从上而下移动的,可以一会儿闪烁一会移动,比如把要显示的东西横置过来,然后闪烁显示一个人的名字,之后移动显示I LOVE YOU.甚至如果有无源蜂鸣器的话,还可以在显示的同时放首歌。 五、proteus仿真 下面是我用proteus做的仿真,仿真效果就不放了,就是那个LED点阵上显示之前在单片机上的LED点阵相同的东西罢了。 PS:字模软件网盘链接: 链接:https://pan.baidu.com/s/1xrshrLyE3dNWbLnDDi8TxQ 提取码:j9u8