在校期间实验制作有关51单片机数字钟设计
一、主要技术指标要求
1、基本要求
1、设计数字钟控制电路,具有基本时钟计时功能;
2、加入闹钟功能,有蜂鸣器进行提醒;
3、24/12小时制切换;
4、整点报时功能;
5、通过单片机自主设置当前时间;
2、扩展功能
1、正计时功能;
2、倒计时功能,当倒计时结束蜂鸣器进行提醒;
3、世界时间功能,通过按键可查看不同时区时间;
二、设计电路介绍
本次设计参考手表功能,由我自己的手表作为模板,设计了12/24小时计时、世界时间、闹钟、倒计时、正计时功能。其中最重要的是通过晶振进行计数,由此有两种方案。
1.方案一:单片机晶振+计数器
本方案以单片机内部晶振产生矩形波,并使用单片机中定时/计数器工作方式二进行计数,达到精准每秒计数的方式。将数据储存在缓存数据中,给到数码管进行显示。
2.方案二:使用DS1302芯片
本方案直接使用DS1302芯片进行准确读数,将时间数据写入芯片,并每秒进行读数,达到给数码管显示的效果。
对比两种方案,我最终选择了方案二。可明显看出使用DS1302不论是精简度,或是准确性皆优于计数器方案,并且单片机无需做过多的计算过程,在运算上可以节省大量时间,不会导致晶体管显示出现闪烁。
数字钟及其电路原理框图如图所示,be那次设计中在,主要的电路为矩阵键盘、复位电路、蜂鸣器、晶体管、DS1302芯片、定时/计数器、晶振电路、电源。他们各自功能将在数字钟使用过程中展示,接下来我会介绍这些电路如何运行。
图3-1 电路原理图
1、矩阵键盘
由单片机原理图可知,矩阵键盘与P1.0-P1.7相连,并都接入高电平。在代码中我们需要进行行扫描以及列扫描从而准确读出哪个按键被按下,并完成指定指令。
2、复位电路
作为单片机基本电路,起到复位、初始化作用。由原理图可知,复位按钮持续按下两个机械周期后,可实现复位功能,有效防止单片机因运行时出现的锁死等问题。
3、蜂鸣器
在我们使用的51单片机中,搭载的是无源蜂鸣器,因此我们需要对其发送矩形波信号使其能够发出声音。
4、定时/计数器
通过晶振得到晶振周期,由12个晶振周期组成一个机械周期。本次设计选用定时器工作方式0,即16位计数器方式。其中自带一个16位计数器,没经过一个机械周期加一,当寄存器中数据记满时,溢出位记1,通知单片机,到达定时扫描数据的效果。
5、晶体管
本次设计所使用的晶体管为共阳极晶体管。因此在管位选以及位显示时都需要对晶体管输入低电平信号才能使其正常工作,即二进制中输出0为亮起或选择。同时选用动态显示,由于人眼的残留效应,使晶体管每次显示一位数字,但高速显示下一个,达到同时显示的效果。
6、DS1302芯片
DS1302 是DALLAS 公司推出的涓流充电时钟芯片,内含有一个实时时钟/日历和31 字节静态RAM,通过简单的串行接口与单片机进行通信。实时时钟/日历电路提供秒、分、时、日、日期、月、年的信息,每月的天数和闰年的天数可自动调整,时钟操作可通过AM/PM 指示决定采用24 或12 小时格式。
四、软件流程图
在本次设计中,所编写的软件流程图如图 所示,其大致流程为:当代码被烧入单片机后,电源通电,进入主函数循环。首先扫描矩阵键盘,然后判断中断是否执行,此处设置中断时间为0.7秒,若中断执行,读取DS1302时间数据,判断是否进入计数或倒计数状态。判断闹钟是否打开,若打开检测时间是否达到闹钟时间,若达到则蜂鸣器报警。若中断未执行则读取RAM数据使用晶体管显示之后重新进入循环。
图4-1 软件流程图
五、仿真图或实测图
1.初始状态
晶体管第一位显示闹钟是否打开,第二位指示24/12小时制,后六位显示时间
图5-1 初始状态图
2.闹钟模式
O表示闹钟打开,此时若时钟达到设定值蜂鸣器报警,C表示关闭闹钟。
图5-2 闹钟模式图
3..12小时制状态
P表示下午,A表示上午
图5-3下午显示图
图5-4上午显示图
4.正/倒计时模式
进入正/倒计时模式,DS1302不会停止计数,仅依靠晶振进行计数,不影响时钟。倒计时完毕蜂鸣器报警。
六、完成设计过程中所遇到的问题及解决方法
1、晶体管显示显示效果不佳
1.1问题详情及分析
由于采用的是晶体管的动态显示,需要在每位晶体管显示期间进行尽可能少的运算操作。起初加入过多功能后,晶体管出现闪烁现象,显示效果极其不佳。
1.2解决方案
加入定时器功能,用每秒进行检测读取数据取代每次显示操作中间进行读取。保留键盘扫描功能,但将其他功能写入每次中断中,有效解决了晶体管闪烁现象。
2.数据读取及运算错误
2.1问题详情及分析
在编写24/12小时制转换代码时,出现运算错误,显示数字与预期不匹配。并且定时器出现不能使用现象。初步判断为运算过程中出现错误。经过更换单片机,修改代码后发现数据并未写入预期位置。
2.2解决方案
修改数据名称,在编写代码时数据名称中有一部分名称中字符重复,导致单片机未能按预期写入,将其修改为完全不同名称或加入下划线及后缀后有效修复该问题。
3.DS1302不能暂停
3.1问题详情及分析
在阅读DS1302芯片使用说明时,清晰写到在秒寄存器的高7位为时钟暂停位,当写入1时时钟暂停。但在实际单片机中使用并未发现此功能。
3.2解决方案
通过更换单片机、修改代码、咨询老师后发现本次设计所使用的单片机并无此功能,因此修改代码,使DS1302芯片继续计数,但停止向其读取数据,使时分秒停留在当前。
七、附录代码
代码展示:
#include <reg52.h>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
sbit SCLK = P1^0;//时钟线
sbit IO= P1^1;//数据线
sbit CE = P1^2;//使能端
sbit DU = P0;//数码管段选
sbit WE = P2;//数码管位选
sbit beep=P3^6;
uchar Mod=0,Alarm=0,Night=0,Setting=0;
uchar Hour,Min,Sec;
uchar nt=0,kp=6,al=0,ct=0,rcot=0,stp=0,ctal=0;
uchar ch,cm,cs;
uchar Time;
uchar AHour,AMin,ASec;
uchar mmsecond=0,second=0;
uchar KeyValue=0;
uchar Seg[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x00};//共阳0-9
uchar zimu[]={0x88,0xc6,0x8c,0xc0};//A,C,P,O
uchar code Segwei[] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};//数码管位选
/*矩阵显示数据7 8 9 19
4 5 6 16
1 2 3 13
10 11 12 22*/
void timer0_init()
//定时器0初始化,每隔50ms溢出1次
{
TMOD=0X01;//定义T0,工作方式1
TH0=0X4B;//初值
TL0=0XFC;
TR0=1;//启动T0
ET0=1;//打开T0中断
EA=1;//打开总中断
}
timer0() interrupt 1//定时器中断函数
{
mmsecond++;//定时器每溢出一次,多50ms
if(mmsecond==20)//定时器溢出20次后,20x50ms=1s,
{
mmsecond=0;
second++;
}
TH0=0X4B;//装载初值
TL0=0XFC;//装载初值
}
void delay(unsigned char z)//毫秒级延时
{
unsigned char x=0,y=0;
for(y=0;y<z;y++);
}
void Scan()//矩阵检测 将数据写入KeyValue
{
P1=0x0f;
if(P1!=0x0f);
{
delay(1000);
if(P1!=0x0f)
{
switch(P1)
{
case 0x0e:KeyValue=6;break;
case 0x0d:KeyValue=3;break;
case 0x0b:KeyValue=0;break;
case 0x07:KeyValue=9;break;
}
P1=0xf0;
if(P1!=0xf0);
{
delay(100);
if(P1!=0xf0)
{
switch(P1)
{
case 0xe0:KeyValue+=1;break;
case 0xd0:KeyValue+=2;break;
case 0xb0:KeyValue+=3;break;
case 0x70:KeyValue+=13;break;
}
}
}
while(P1!=0xf0);
}
}
}
void display(uchar H,uchar M,uchar S)
{
static uchar wei=0;
P0 = 0XFF;//清除段码
P2= Segwei[wei];
switch(wei)
{
case 0: if(Alarm==1)P0=0xc0;
//显示o 闹钟打开
else P0=0xc6;break;
//显示c 闹钟关闭
case 1: if(Mod==0)P0 = 0xff;
else
{
if(Night)
{
P0=0x8c;
//下午时间显示p
}
else P0=0x88;
//上午时间显示A
}
break;
case 2: P0 = Seg[H / 10]; break;
case 3: P0 = Seg[H % 10 ]&0x7f; break;
case 4: P0 = Seg[M / 10]; break;
case 5: P0 = Seg[M % 10]&0x7f; break;
case 6: P0 = Seg[S / 10 ]; break;
case 7: P0 = Seg[S % 10]; break;
}
wei++;
if(wei>7)wei=0;
}
void Write_DS1302_DAT(uchar cmd, uchar dat)//写数据,先写控制字,再写数据
{
uchar i;
CE=0;//拉低使能端
SCLK=0;//拉低时钟线
CE=1;//拉高使能端,产生上升沿开始写数据
for(i=0;i<8;i++)
//写控制字,每次写一位
{
SCLK=0;//拉低时钟线
IO=cmd&0x01;//写1位数据,从最低位开始写
SCLK=1;//拉高时钟总线,产生上升沿数据,被DS1302读走
cmd>>=1;//右移一位
}
for(i=0;i<8;i++)//写数据
{
SCLK=0;//拉低时钟线
IO=dat&0x01;
//写1位数据,从最低位开始写
SCLK=1;//拉高时钟总线,产生上升沿数据,被DS1302读走
dat>>=1;//右移一位
}
}
uchar Read_DS1302_DAT(uchar cmd)//读DS1302数据:
{
uchar i, dat;
CE=0;//拉低使能端
SCLK=0;//拉低时钟线
CE=1;//拉高使能端,产生上升沿开始写数据
for(i=0;i<8;i++)
//写控制字,每次写一位
{
SCLK=0;//拉低时钟线
IO=cmd&0x01;
//写1位数据,从最低位开始写
SCLK=1;//拉高时钟总线,产生上升沿数据,被DS1302读走
cmd>>=1;//右移一位
}
for(i=0;i<8;i++)//每次读1位,读8次
{
SCLK=0;//拉低时钟线,产生下降沿,DS1302把数据放在IO线上
dat>>=1;//数据右移一位
if(IO)
dat|=0x80;//写数据
SCLK=1;//拉高时钟总线,以备下次产生下降沿
}
return dat;//返回读出数据
}
uchar Dat_BCD(uchar dat)//数据转BCD码
{
uchar dat1, dat2;
dat1 = dat / 10;
dat2 = dat % 10;
dat2 = dat2 + dat1 * 16;
return dat2;
}
uchar BCD_Dat(uchar dat)//BCD码转数据
{
uchar dat1, dat2;
dat1 = dat / 16;
dat2 = dat % 16;
dat2 = dat2 + dat1 * 10;
return dat2;
}
void write_dat(uchar H,uchar M,uchar S)
{
Write_DS1302_DAT(0x8e,0);//关闭写保护
Write_DS1302_DAT(0x80,Dat_BCD(S));//设置秒寄存器第七位为1,并设置秒数为20s
Write_DS1302_DAT(0x82, Dat_BCD(M));//设置初始分钟数为15min
Write_DS1302_DAT(0x84,Dat_BCD(H));//设置初始小时数为13h
Write_DS1302_DAT(0x8e,0x80);//打开写保护
}
void count()
{
Sec++;
if(Sec==60)
{
Min++;
Sec=0;
}
if(Min==60)
{
Hour++;
Min=0;
}
}
void recot()
{
if(Sec==0)
{
if(Min==0&&Hour==0)
{
stp=1;
ctal=1;
}
else
{
if(Min==0)
{
Hour--;
Min=59;
Sec=60;
}
else
{
Min--;
Sec=60;
}
}
}
if(stp!=1)Sec--;
}
void detect_key()
{
switch(KeyValue)
{
case 1 :ct^=1;Hour=0;Min=0;Sec=0;stp=1;break;//进入计数模式
case 2:rcot^=1;break;//倒计时模式
case 3:Sec++;break;
case 4:Sec=0;break;//秒清零
case 5:if(Min==59)Min=0;
else Min++;break;//分+1
case 6:if(Min==0)Min=59;
else Min--;break;//分-1
case7:Setting=1;break;//进入设置状态
case 8:if(Hour==23)Hour=0;
else Hour++;break;//时+1
case 9:if(Hour==0)Hour=23;
else Hour--;break;//时-1
case10:Alarm=1;Setting=1;break;
//打开闹钟,进入设置状态
case11:AHour=Hour;AMin=Min;
ASec=Sec;Setting=0;break;
//结束设置状态
case12:Alarm=0;al=0;break;//关闭闹钟
case 13:stp^=1;break;
case16:Mod^=1;break;
//24h/12h切换 case19:write_dat(Hour,Min,Sec);Setting=0;break;
//结束设置状态,写入芯片
case22:al=0;ctal=0;break;//停止叫声
case 0:break;
}
KeyValue=0;
}
void main()
{
uchar i;
timer0_init();
//24h开关 1显示24小时,0显示12小时
Write_DS1302_DAT(0x8e,0);
//关闭写保护
Write_DS1302_DAT(0x80,Dat_BCD(45));
//设置秒寄存器第七位为1,并设置秒数为20s
Write_DS1302_DAT(0x82, Dat_BCD(59));//设置初始分钟数为15min
Write_DS1302_DAT(0x84,Dat_BCD(23));
//设置初始小时数为13h
Write_DS1302_DAT(0x8e,0x80);
//打开写保护
while(1)
{
scan();
if(second==1)
{
detect_key();//按键检测
if(ct==1&&rcot==0&&stp==0) count();
else if(ct==1&&rcot==1&&stp==0) recot();
else if(ct==1&&rcot==1&&stp==1&&ctal==1)
{
for(i=0;i<10;i++)
{
beep=0;
delay(1000);
//声音频率
beep=1;
}
}
else if(ct==0)
{
if(Min==59&&Sec==59)
{
nt=1;
kp=0;
}
if(nt==1)
{
if(kp<6)kp++;
else nt=0;
}
if(kp<6||al==1)
{
for(i=0;i<10;i++)
{
beep=0;
delay(1000);
//声音频率
beep=1;
}
}
if(!Setting)
//等于0时正常度数
{
Write_DS1302_DAT(0x8e,0);//关闭写保护
Sec=BCD_Dat(Read_DS1302_DAT(0x81));//读秒寄存器(并进行BCD码转换)
Min=BCD_Dat(Read_DS1302_DAT(0x83));//读分寄存器(并进行BCD码转换)
Hour=BCD_Dat(Read_DS1302_DAT(0x85));//读时寄存器(并进行BCD码转换)
Write_DS1302_DAT(0x8e,0x80);//打开写保护
}
if(Alarm==1)
{
if(Hour==AHour&&Min==AMin&&Sec==ASec) al=1;
}
}
if(Mod==1)
{
if(Hour>12)
{
Time=Hour-12;
Night=1;
}
else
{
Time=Hour;
Night=0;
}
}
else Time=Hour;
second=0;
}
display(Time,Min,Sec);
}
}