初学单片机的菜鸟们,都很想自己制作一款单片机电子钟,放在自己的电脑桌上,展现一下学习单片机的成果,现在就来讲一讲制作这款遥控单片机数码管时钟的过程。单片机采用STC89C52RC单片机,时钟芯片采用DS1302控时,18B20采集温度,红外遥控(通用电视机顶盒遥控器),万年历显示,一路闹钟,自动/手动切换显示,超低功耗掉电工作模式(适合电池供电),拥有备用电源掉电不掉时。数码管经消影处理,亮度显示均匀,两个二位一体数码管是互相倒过来安装的,从而让两个小数点在中间形成一个冒号。完整程序下载:
遥控单片机数码管时钟程序(无闹钟版).rar
(47.93 KB, 下载次数: 292)
测量温度时的情况:

制作一款单片机电子钟需要那些元器件呢?请看下图,所需要的元件名称、外形及数量都在图中。

数码管的选择,本作品要选用的是二位一体共阳极0.56寸数码管,下图有两种二位一体共阳极数码管,但左边的共有18个引脚,右边的只有10个,为了方便布线,我们选择右边的10引脚哪种数码管。数码管的引脚,本作品将两个数码管相倒安装,所以其引脚顺序也不一样了,请对照下面两幅图片,安装时看清认准。
这里就不给出电原理图,而直接给出实物连接图,下面给出的图,布的是双层板,新手们先从单层的练起,然后再缩小布局,采用双层板。图中蓝色线表示洞洞板的布线,红色线表示顶层线。安装时整板是根据洞洞板来布局的,焊接时,红线作为飞线来布,图中的Z1表示接线端子,H1是一体化红外接受头,数码管一定是共阳极数码管,焊接时先焊飞线,再焊元件。数码管的四角都有突起,所以放在洞洞板上与板面之间会有缝隙,可以将飞线隐藏在数码管的下面。本作品中的DS1302没有装备用电池。本程序加入了自动软件校时,但由于硬件本身,每个人做出来的时间误差都不一样,只要修改校时数据就可以了,只要对时准确,天误差一般都可调整到小于一秒。

下面说一下遥控器的用法,是不是很熟悉啊,电视机顶盒的遥控器......
电源键:进入/退出待机状态,待机时第一个小数点闪烁
待机键:开启/关闭掉电模式
声道键:切换自动/手动显示
确认键:手动切换显示界面
电视键:时间小时/月加一
点播键:时间小时/月减一
广播键:时间分钟/年/日加一
资讯键:时间分钟/年/日减一
调整方法:在各自的显示界面,按加一或减一键即可调整时间/年份/日月
制作遥控单片机数码管时钟的注意事项:
1、先说一下数码管断码的问题,大家可以看到,一共用了两个二位一体的数码管,第一个是正放的,第二个是反放的,所以两个数码管的引脚顺序是不一样的,再有就是根据硬件来接线的,为了方便走线,所以数码管的段码引脚和P0口的连接不是按照a,b,c,d,e,f,g,dp的顺序来的,所以大家可能会觉得程序里的数码管段码数组会很奇怪,只要大家自己根据自己的接法改一下就好了。
2、硬件布局方面,由于都是没有钱的学生,那种专门的印制的电路板我们是做不起的,但是大家都应该有洞洞板(万用板)。洞洞板是个好东西,在洞洞板上做东西能极大的提高我们硬件布局和接线的能力,要尽量少使用飞线,元器件的摆放要合理,比如说晶振与芯片的连接不能太远,18B20温度传感器要远离单片机,数码管等会发热的元器件,不然就不准了......
3、软件方面,理论上说,软件是万能的,但有时候一个复杂的软件所完成的工作只需几个简单的硬件就能完成,我们在考虑成本的基础上最好还是选择硬件,毕竟现在很多的电子元件都很便宜,因为复杂的软件会增加CPU的负担且降低系统工作的稳定性。当然,反之亦然......
4、关于DS1302发热的问题,可能很多朋友都遇到过DS1302发热的问题,首先请查看你的DS1302是不是1108批次的货(芯片上面有),这个批次的货有缺陷,一般情况下换几个1302会好的。再有试着将VCC1和VCC2调换这接一下试试。如果再不行,请检查程序。
5、由于程序及电路图这里不好贴出来,需要的爱好者可以自己下载
遥控单片机数码管时钟资料来源于网络,这里得感谢一叶知秋无私提供
源程序:1.头文件DS18B20_3.H:
2.主程序:
- /**********************************WAYNE*********************************/
- #include<reg52.h>
- #include<intrins.h>
- #include"DS18B20_3.H" //18B20温度传感器头文件,18B20的服务程序都在这里
- #define uchar unsigned char
- #define uint unsigned int
- unsigned int distemp=8; //红外接收返回值
- unsigned char IRCOM[7];
- unsigned char temp;
- unsigned char sec, min, hour, day, month, year;
- unsigned char aa=0; //遥控器中间变量
- unsigned char bb=1; //显示界面中间变量
- unsigned char cc=0; //自动切换中间变量
- unsigned char dd=0; //掉电模式中间变量
- unsigned char ee=0;
- unsigned char flag;
- uchar code table[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; //数码管段码
- sbit ACC0 = ACC^0;
- sbit ACC7 = ACC^7;
- sbit T_CLK = P3^5; //实时时钟时钟线引脚
- sbit T_IO = P3^6; //实时时钟数据线引脚
- sbit T_RST = P3^7; //实时时钟复位线引脚
- sbit Y1=P2^6; //第一位数码管共阳极引脚
- sbit Y2=P2^3; //第二位数码管共阳极引脚
- sbit Y3=P2^1; //第三位数码管共阳极引脚
- sbit Y4=P2^0; //第四位数码管共阳极引脚
- sbit led=P0^7; //数码管小数点阴极引脚
- sbit IRIN = P3^2; //红外接收器数据线与单片机引脚之间的定义
- /*********************************延时函数**************************************/
- void Delay_1ms(uint i) //1ms延时
- {
- uchar x,j;
- for(j=0;j<i;j++)
- for(x=0;x<=148;x++);
- }
- void delay1(unsigned char x) //延时函数x*0.14MS
- {
- unsigned char i;
- while(x--)
- {
- for (i = 0; i<13; i++) {}
- }
- }
- /**********************************往DS1302写入1Byte数据*************************************/
- void v_RTInputByte(uchar ucDa)
- {
- uchar i;
- ACC = ucDa;
- T_RST = 1;
- for(i=8; i>0; i--)
- {
- T_IO = ACC0;
- T_CLK = 1;
- T_CLK = 0;
- ACC = ACC >> 1;
- }
- }
- /******************************从DS1302读取1Byte数据*****************************************/
- uchar uc_RTOutputByte(void)
- {
- uchar i;
- T_RST = 1;
- for(i=8; i>0; i--)
- {
- ACC = ACC >>1;
- T_IO=1;
- ACC7 = T_IO;
- T_CLK = 1;
- T_CLK = 0;
- }
- return(ACC);
- }
- /*******************************往DS1302写入数据****************************************/
- void v_W1302(uchar ucAddr, uchar ucDa)
- {
- T_RST = 0;
- T_CLK = 0;
- T_RST = 1;
- v_RTInputByte(ucAddr); // 写地址
- _nop_();
- _nop_();
- v_RTInputByte(ucDa); // 写1Byte数据
- T_CLK = 1;
- T_RST = 0;
- }
- /**********************************读取DS1302某地址的数据*************************************/
- uchar uc_R1302(uchar ucAddr)
- {
- uchar ucDa;
- T_RST = 0;
- T_CLK = 0;
- T_RST = 1;
- v_RTInputByte(ucAddr); //写地址,命令
- _nop_();
- _nop_();
- ucDa = uc_RTOutputByte(); //读1Byte数据
- T_CLK = 1;
- T_RST = 0;
- return(ucDa);
- }
- /*********************************DEC码转换为BCD码**************************************/
- uchar dectobcd(uchar dec)
- {
- uchar bcd;
- bcd = 0;
- while(dec >= 10)
- {
- dec -= 10;
- bcd++;
- }
- bcd <<= 4;
- bcd |= dec;
- return bcd;
- }
- /*******************************BCD码转换为DEC码****************************************/
- uchar bcdtodec(uchar bcd)
- {
- uchar data1;
- data1 = bcd & 0x0f; //取BCD低4位
- bcd = bcd & 0x70; //剔除BCD的最高位和低4位。
- data1 += bcd >> 1;
- data1 += bcd >> 3; //用位移代替乘法运算
- return data1;
- }
- /*********************************往DS1302中写入数据**************************************/
- void Write_DS1302Init(void)
- {
- v_W1302(0x8e,0x00); //屏蔽一下语句,则掉电走时有效;屏蔽则掉电走时失效,且每次上电后显示12:00(时)/2012(年)/1(月)1(日)
- v_W1302(0x80,0x00); //写入秒
- v_W1302(0x82,0x00); //写入分
- v_W1302(0x84,0x12); //写入小时
- v_W1302(0x86,0x01); //写入日
- v_W1302(0x88,0x01); //写入月
- v_W1302(0x8a,0x00); //写入星期
- v_W1302(0x8c,0x12); //写入年
- v_W1302(0x8e,0x80);
- }
- /*********************************数码管位选码**************************************/
- void Y_1() //选通第一位数码管
- {
- Y1=0;
- Y2=1;
- Y3=1;
- Y4=1;
- }
- void Y_2() //选通第二位数码管
- {
- Y1=1;
- Y2=0;
- Y3=1;
- Y4=1;
- }
- void Y_3() //选通第三位数码管
- {
- Y1=1;
- Y2=1;
- Y3=0;
- Y4=1;
- }
- void Y_4() //选通第四位数码管
- {
- Y1=1;
- Y2=1;
- Y3=1;
- Y4=0;
- }
- void Y_5() //切断所有数码管,用于数码管消隐
- {
- Y1=1;
- Y2=1;
- Y3=1;
- Y4=1;
- }
- /*******************************显示程序****************************************/
- void Run_DS1302(void)
- {
- v_W1302(0x8f, 0x00);
- sec = bcdtodec(uc_R1302(0x81)); //读出DS1302中的秒
- min = bcdtodec(uc_R1302(0x83)); //读出DS1302中的分
- hour = bcdtodec(uc_R1302(0x85)); //读出DS1302中的小时
- day = bcdtodec(uc_R1302(0x87)); //读出DS1302中的日
- month = bcdtodec(uc_R1302(0x89)); //读出DS1302中的月
- year = bcdtodec(uc_R1302(0x8d)); //读出DS1302中的年
- v_W1302(0x8f,0x80);
- if(distemp == 0)
- { distemp = 8;
- aa++;
- }
- if(distemp ==7)
- { distemp = 8;
- ee++;
- }
- if(ee == 2)
- ee=0;
- if(aa==4) //判断显示数据
- aa=0;
- if(aa == 0)
- {
- bb = 1; //显示时间
- }
- if(aa == 1)
- {
- bb = 2; //显示温度
- }
- if(aa == 2)
- {
- bb = 3; //显示年份
- }
- if(aa == 3)
- {
- bb = 4; //显示日期
- }
- if(ee == 1)
- bb = 5;
- if(bb == 1) //时间显示界面
- { if(hour / 10 % 10!=0) //十位为0则不显示
- {P1 = ~table[hour / 10 % 10]; //显示小时的十位数据
- led=1;
- Y_1(); //选通第一位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- }
- P1 = ~table[hour % 10]; //显示小时的个位数据
- if(sec%2==0) //判断是否秒闪烁
- led=1;
- else led=0;
- Y_2(); //选通第二位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- P1 = ~table[min / 10 % 10]; //显示分钟的十位数据
- if(sec%2==0) //判断是否秒闪烁
- led=1;
- else led=0;
- Y_3(); //选通第三位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- P1 = ~table[min % 10]; //显示分钟的个位数据
- led=1;
- Y_4(); //选通第四位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- }
- if(bb == 2) //温度显示界面
- {
- P1 = ~table[(flag-1) / 10 % 10]; //显示温度的十位数据
- led=1;
- Y_2(); //选通第二位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- P1 = ~table[(flag-1) % 10]; //显示温度的个位数据
- led=1;
- Y_3(); //选通第三位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- P1 = ~0x58; //显示摄氏温度标志
- led=0;
- Y_4(); //选通第四位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- }
- if(bb == 3) //年份显示界面
- {
- P1 = ~0x6e; //显示年份千位数据,默认为2
- led=1;
- Y_1(); //选通第一位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- P1 = ~0x77; //显示年份百位数据,默认为0
- led=1;
- Y_2(); //选通第二位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- P1 = ~table[year / 10 % 10]; //显示年份十位数据
- led=1;
- Y_3(); //选通第三位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- P1 = ~table[year % 10]; //显示年份个位数据
- led=1;
- Y_4(); //选通第四位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- }
- if(bb == 4) //月日显示界面
- { if(month / 10 % 10!=0) //十位为0则不显示
- {P1 = ~table[month / 10 % 10]; //显示月十位数据
- led=1;
- Y_1(); //选通第一位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- }
- P1 = ~table[month % 10]; //显示月个位数据
- led=0;
- Y_2(); //选通第二位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- if(day / 10 % 10!=0) //十位为0则不显示
- {P1 = ~table[day / 10 % 10]; //显示日十位数据
- led=1;
- Y_3(); //选通第三位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- }
- P1 = ~table[day % 10]; //显示日个位数据
- led=1;
- Y_4(); //选通第四位数码管
- Delay_1ms(1); //短暂停留
- P1 = 0xff; //数码管消隐
- led=1;
- Y_5(); //切断所有数码管
- }
- if(bb == 5)
- {
- P1=0xff;
- if(sec%2==0) //判断是否秒闪烁
- led=1;
- else led=0;
- Y_1(); //选通第四位数码管
- Delay_1ms(1); //短暂停留
- }
- }
- /**********************************自动切换程序*************************************/
- void zdqh()
- {
- if(distemp==6)
- {distemp=8;
- cc++;
- }
- if(cc==2) //切换为手动显示
- cc=0;
- if(cc==1) //切换为自动循环显示
- {
- if(sec%10==0) //每10秒内,前7秒显示时间界面
- aa = 0;
- if(sec==7||sec==37) //7秒和37秒切换为温度显示
- aa = 1;
- if(sec==17||sec==47) //17秒和47秒切换为年份显示
- aa = 2;
- if(sec==27||sec==57) //27秒和57秒切换为月日显示
- aa = 3;
- }
- }
- /*********************************调节程序**************************************/
- void tj()
- {
- if(distemp == 1) //判断按下的键值
- {distemp=8; //返回值回位
- if(bb==1) //判断当前显示界面
- {
- hour++; //小时加一调节
- if(hour==24) //限制小时数据在0~23之间
- hour=0;
- temp=dectobcd(hour); //将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00); //禁止写保护
- v_W1302(0x84,temp); //写入小时数据
- v_W1302(0x80,0x00);
- v_W1302(0x8e,0x80); //打开写保护
- }
- if(bb==4) //判断当前显示界面
- {
- month++; //月加一调节
- if(month==13) //限制月数据在1~12之间
- month=1;
- temp=dectobcd(month); //将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00); //禁止写保护
- v_W1302(0x88,temp); //写入月数据
- v_W1302(0x8e,0x80); //打开写保护
- }
- }
- if(distemp == 2) //判断按下的键值
- {distemp=8; //返回值回位
- if(bb==1) //判断当前显示界面
- {
- min++;//分钟加一调节
- if(min==60)//限制分钟数据在0~59之间
- min=0;
- temp=dectobcd(min);//将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00);//禁止写保护
- v_W1302(0x82,temp);//写入分钟数据
- v_W1302(0x80,0x00);
- v_W1302(0x8e,0x80);//打开写保护
- }
- if(bb==3)//判断当前显示界面
- {
- year++;//年份加一调节
- if(year==100)//限制年份数据在0~99之间
- year=0;
- temp=dectobcd(year);//将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00);//禁止写保护
- v_W1302(0x8c,temp);//写入年数据
- v_W1302(0x8e,0x80);//打开写保护
- }
- if(bb==4)//判断当前显示界面
- {
- day++; //日加一调节
- if(day==32) //限制日数据在1~31之间
- day=1;
- temp=dectobcd(day); //将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00); //禁止写保护
- v_W1302(0x86,temp); //写入日数据
- v_W1302(0x8e,0x80); //打开写保护
- }
- }
- if(distemp == 3) //判断按下的键值
- {distemp=8; //返回值回位
- if(bb==1) //判断当前显示界面
- {
- hour--; //小时减一调节
- if(hour==-1) //限制小时数据在0~23之间
- hour=23;
- temp=dectobcd(hour); //将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00); //禁止写保护
- v_W1302(0x84,temp); //写入小时数据
- v_W1302(0x80,0x00);
- v_W1302(0x8e,0x80); //打开写保护
- }
- if(bb==4) //判断当前显示界面
- {
- month--; //月减一调节
- if(month==0) //限制月数据在1~12之间
- month=12;
- temp=dectobcd(month); //将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00); //禁止写保护
- v_W1302(0x88,temp); //写入月数据
- v_W1302(0x8e,0x80); //打开写保护
- }
- }
- if(distemp == 4) //判断按下的键值
- {distemp=8; //返回值回位
- if(bb==1) //判断当前显示界面
- {
- min--; //分钟减一调节
- if(min==-1) //限制分钟数据在0~59之间
- min=59;
- temp=dectobcd(min); //将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00); //禁止写保护
- v_W1302(0x82,temp); //写入分钟数据
- v_W1302(0x80,0x00);
- v_W1302(0x8e,0x80); //打开写保护
- }
- if(bb==3) //判断当前显示界面
- {
- year--; //年份减一调节
- if(year==-1) //限制年份数据在0~99之间
- year=99;
- temp=dectobcd(year); //将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00); //禁止写保护
- v_W1302(0x8c,temp); //写入年数据
- v_W1302(0x8e,0x80); //打开写保护
- }
- if(bb==4) //判断当前显示界面
- {
- day--; //日减一调节
- if(day==0) //限制日数据在1~31之间
- day=31;
- temp=dectobcd(day); //将十进制数转换成十六禁止数
- v_W1302(0x8e,0x00); //禁止写保护
- v_W1302(0x86,temp); //写入日数据
- v_W1302(0x8e,0x80); //打开写保护
- }
- }
- }
- /*******************************掉电模式程序****************************************/
- void xmms()
- {
- if(dd == 1) //单片机进入掉电工作模式,该模式下只有首位数码管的小数点常亮
- {
- P0=~0xff; //在掉电模式下,只有第一位数码管的小数点亮
- led=1;
- Y_5();
- aa=0;
- ee=0;
- PCON=0x02; //设置掉电模式寄存器,这句一定要放在该子程序的最后
- }
- }
- /********************************自动校时***************************************/
- void zdjs()
- {
- if(hour==3&&min==0&&sec==30)
- {
- v_W1302(0x8e,0x00);
- v_W1302(0x80,0x00);
- v_W1302(0x8e,0x80);
- }
- }
- /********************************主函数***************************************/
- void Main(void)
- {
- EA=1; //打开总中断
- EX0=1; //允许外部0中断
- IT0=1; //设置外部中断0为边沿方式触发
- IRIN=1;
- Write_DS1302Init();
- while(1) //无限循环以下子程序
- {
- Run_DS1302(); //显示程序
- flag=ReadTemperature(); //将18b2头文件运行返回的函数结果送到变量flag中,用于显示
- tj(); //调节程序
- xmms(); //休眠模式
- zdqh(); //自动切换
- zdjs(); //自动校时
- }
- }
- /*********************红外接收程序**********************/
- void IR_IN() interrupt 0 //外部中断0
- {
- unsigned char p,q,R=0;
- EA = 0;
- delay1(15);
- if (IRIN==1)
- { EA =1;
- return;
- }
- while (!IRIN)
- {delay1(1);}
- for (p=0;p<4;p++)
- {
- for (q=0;q<8;q++)
- {
- while (IRIN)
- {delay1(1);}
- while (!IRIN)
- {delay1(1);}
- while (IRIN)
- {
- delay1(1);
- R++;
- if (R>=30)
- { EA=1;
- return;}
- }
- IRCOM[p]=IRCOM[p] >> 1;
- if (R>=8) {IRCOM[p] = IRCOM[p] | 0x80;}
- R=0;
- }
- }
- if (IRCOM[2]!=~IRCOM[3])
- { EA=1;
- return; }
- switch(IRCOM[2])
- {
- case 0x07: //显示切换键
- distemp = 0;
- break;
- case 0x44: //左加一键
- distemp = 1;
- break;
- case 0x45: //右加一键
- distemp = 2;
- break;
- case 0x46: //左减一键
- distemp = 3;
- break;
- case 0x47: //右减一键
- distemp = 4;
- break;
- case 0x16: //掉电模式开关键
- distemp = 5;
- break;
- case 0x10: //自动切换开关键
- distemp = 6;
- break;
- case 0x1c: //待机模式开关键
- distemp = 7;
- break;
- }
- if(distemp == 5) //开启、关闭掉电模式,因掉电模式只能由外部中断唤醒,所以下面的程序必须放在中断函数中
- {distemp = 8; //返回值回位
- dd++;
- }
- if(dd == 2)
- dd = 0;
- if(dd == 0) //退出掉电模式
- {PCON=0x00; //设置掉电模式工作寄存器
- }
- if(dd == 1) //掉电模式下,其他按键无效
- distemp = 8; //不返回任何键值
- if(ee==1)
- {if(distemp==0||distemp==1||distemp==2||distemp==3||distemp==4||distemp==6)
- distemp = 8;
- }
- EA = 1; //打开总中断
- }
复制代码
|