基于51单片机的电子时钟设计
本设计基于STC89C52RC单片机开发电子闹钟系统,采用DS1302实时时钟模块实现精准计时,断电后通过纽扣电池保持时间数据。系统包含4个功能按键用于时间/闹钟设置,通过8位数码管显示时间信息,当闹钟时间到达时触发蜂鸣器提醒。硬件设计包括主控模块、时钟模块、按键模块、显示模块和报警模块,软件采用C语言编写,实现时间读取、按键处理、闹钟判断等功能。系统具有低功耗、稳定性强等特点,后续可扩展LCD显
一、设计概述
本设计以STC89C52RC单片机为控制核心,搭载DS1302实时时钟模块实现精准时间读取与存储,配合4个功能按键完成“标准时间设置”与“单闹钟设置”,最终通过8位共阳数码管直观呈现时间与闹钟状态,整体电路具备低功耗、稳定性强、操作便捷的特点,可满足时间校准与闹钟提醒需求。
二、核心硬件选型与功能
1. 主控模块:STC89C52RC单片机
作为系统“大脑”,负责接收DS1302的时间数据、解析按键指令、控制LCD显示及触发闹钟提醒,其8位MCU架构、4KB片内Flash及32个I/O口,完全适配本设计的低复杂度控制需求,且支持5V直流供电,与其他模块兼容性强。

2. 实时时钟模块:DS1302
核心功能:实现年、月、日、时、分、秒的精准计时,支持断电后通过备用纽扣电池(CR2032)保持时间不丢失,解决单片机断电后时间重置的问题。
通信方式:采用3线制串行通信(SCLK时钟线、I/O数据线、RST复位线),仅需占用单片机3个I/O口,简化硬件接线。

3. 按键模块:4个独立按键
按功能定义为4个核心按键,具体功能如下:
时间设置键(Set_Time):触发“时间设置”模式,短按切换设置项(如从“时”切换到“分”)设置完毕后再次按下返回实时时间显示模式。
闹钟设置键(Set_NZ):触发“闹钟设置”模式,短按切换设置项(如从“时”切换到“分”)设置完毕后再次按下返回实时时间显示模式。
加键(Time+):在设置模式下,对当前选中的设置项(时/分/秒或闹钟时/分)进行数值加1,数值溢出时自动进位(如“23时”加1变为“00时”)。
减键(Time-):与加键功能相反,对当前选中的设置项进行数值减1,数值不足时自动借位(如“00分”减1变为“59分”)。

4. 显示模块:8位共阳数码管
显示内容:常规模式显示“当前标准时间”(格式:HH-MM-SS),时间/闹钟设置模式下显示“设置时间”被设置的时间快速闪烁。
优势:编程简单,成本低,入门即可应用。
劣势:直驱时IO口占用较多,不适合大型项目使用(可使用其他方式扩展使用)

5. 闹钟提醒模块:蜂鸣器
当“当前时间”与“设置的闹钟时间”完全匹配(时、分一致)时,单片机触发I/O口输出高电平,驱动蜂鸣器发出持续鸣叫,持续5s后自动关闭鸣叫。

三、软件设计流程
软件采用C语言编程,基于Keil C51开发环境编译,核心流程分为“主程序循环”“DS1302时间读取”“按键处理”“闹钟判断”四大模块,具体逻辑如下:
1. 初始化模块
系统上电后,首先完成初始化操作:
1. 初始化DS1302:检查DS1302是否正常工作,若为首次上电,向其写入初始时间(如“2025-01-01 00:00:00”),并默认关闭闹钟。
2. 初始化I/O口:将连接按键的I/O口设为输入模式,连接蜂鸣器的I/O口设为输出模式(初始高电平,蜂鸣器不工作)。
2. 主程序循环
主循环以“1秒”为周期执行,流程如下:
1. 读取DS1302时间:通过串行通信读取DS1302内存储的年、月、日、时、分、秒数据,并转换为十进制格式(DS1302存储数据为BCD码,需转换后才能显示)。
2. 判断闹钟状态:将读取的“当前时、分”与单片机内存储的“闹钟时、分”对比,若一致且闹钟处于“开启”状态,则驱动蜂鸣器鸣叫;若用户按下“确认键”,则停止鸣叫并保持闹钟关闭状态。
3.数码管显示更新:将转换后的“当前时间”显示在8位数码管上。
4. 按键扫描与处理:循环扫描4个按键,若检测到按键按下(需消抖处理,避免机械抖动导致误操作),则进入对应功能逻辑:
- 按下“Set_Time或Set_NZ键”:切换至“时间设置”或“闹钟设置”模式(可通过短按多次切换),此时数码管对应设置项(如“时”)闪烁,提示用户当前可修改项。
- 按下“Time+/Time-键”:在设置模式下,对闪烁的设置项进行“加1”或“减1”操作,数值范围符合时间逻辑(如“时”0-23,“分”0-59)。
四、硬件仿真接线图(仿真省略了最小系统)

五、系统测试与功能验证
时间读取与显示:上电后,数码管正常显示DS1302的初始时间(仿真直接调用系统实时时间,可在程序中修改显示预设时间),秒数每秒递增,断电后重新上电,时间仍保持连续。

时间设置:按下“Set_Time/Set_NZ键”,数码管“时”位闪烁,通过“Time+/Time-键”修改小时。循环短按设置键,时间更新为设置值,功能正常。

闹钟设置与提醒:切换至闹钟设置模式,设置“08:00”并开启闹钟,当当前时间达到08:00时,蜂鸣器自动鸣叫,5s 后鸣叫停止。
六、设计总结
本设计基于51单片机实现了电子闹钟的核心功能,通过DS1302确保时间精准性,4个按键简化操作逻辑,数码管实现直观显示,整体方案硬件成本低、软件逻辑清晰、稳定性强。
后续可进一步优化:使用LCD液晶屏幕显示、增加“多组闹钟设置”功能、通过LED替代蜂鸣器实现静音提醒、加入温度检测模块显示环境温度,提升设计的实用性与扩展性。
七、部分程序
main.c
#include "STC89.h"
#include "Timer.h"
#include "ds1302.h"
#include "Key.h"
#include "SEG.h"
#include "Buzz.h"
#define uint8_t unsigned char
#define uint16_t unsigned int
uint16_t count;
uint8_t T;
void main(void)
{
Timer0_Init();
// DS1302_init(); //仿真调用实时时间
while(1)
{
Key_can();
if(Set_Time==0&&Set_NZ==0)
{DIS_Time();}
if(Set_Time!=0&&Set_NZ==0)
{Ds1302_Stop();Dis_Set_Time();}
if(Set_Time==0&&Set_NZ!=0)
{Dis_Set_NZ();}
if((sj[2]==NZ_sj[2])&&(sj[1]==NZ_sj[1])&&(sj[0]>=NZ_sj[0])&&T<5)
{TR0 = 1; Buzz_Play(count);}
if(sj[1]!=NZ_sj[1])
{TR0 = 0;T=0;}
}
}
/* Timer0 interrupt routine */
void tm0_isr() interrupt 1
{
TL0 = T1MS; //reload timer0 low byte
TH0 = T1MS >> 8; //reload timer0 high byte
count++;
if (count == 1000) //1ms * 1000 -> 1s
{
T++;
count = 0;
}
}
key.c
#include "Key.h"
#include "Delay.h"
#include "ds1302.h"
uint8_t New_sj[]={56, 59, 23, 30, 11, 6, 98}; //读取时间缓存区域
//秒 分 时 日 月 星期 年
uint8_t NZ_sj[]={0, 0, 0, 30, 11, 6, 98}; //读取时间缓存区域
//秒 分 时 日 月 星期 年
uint8_t Set_Time,Set_NZ;
void Key_can(void)
{
unsigned char i;
unsigned char add=0x80; //定义初始写入地址
/***********************/
if(k1==0)//Set_Time
{
Delay10ms();
if(k1==0)
{
Set_Time++;
if(Set_Time==4)
{
Write_Ds1302(0x8e,0x00); //对WP置零,允许写操作
for(i=0;i<7;i++) //循环7次依次
{ //
Write_Ds1302(add,New_sj[i]); //初始化 秒 分 时 日 月 星期 年
add=add+2; //变更写入地址
}
Write_Ds1302(0x8e,0x80); //开启写保护
Ds1302_Start();
Set_Time=0;
}
}
while(k1==0);
}
/***********************/
if(k2==0)//Set_NZ
{
Delay10ms();
if(k2==0)
{
Set_NZ++;
if(Set_NZ==4)
{Set_NZ=0;}
}
while(k2==0);
}
/***********Set_Tiem************/
if(Set_Time==1)//Hour
{
if(k3==0) //+
{
Delay10ms();
if(k3==0)
{
New_sj[2]++;
if(New_sj[2]==24)
{New_sj[2]=0;}
while(!k3);
}
}
if(k4==0) //-
{
Delay10ms();
if(k4==0)
{
if(New_sj[2]==0)
{New_sj[2]=24;}
New_sj[2]--;
while(!k4);
}
}
}
//////////////////////////
if(Set_Time==2)//Min
{
if(k3==0) //+
{
Delay10ms();
if(k3==0)
{
New_sj[1]++;
if(New_sj[1]==60)
{New_sj[1]=0;}
while(!k3);
}
}
if(k4==0) //-
{
Delay10ms();
if(k4==0)
{
if(New_sj[1]==0)
{New_sj[1]=60;}
New_sj[1]--;
while(!k4);
}
}
}
//////////////////////////
if(Set_Time==3)//Sec
{
if(k3==0) //+
{
Delay10ms();
if(k3==0)
{
New_sj[0]++;
if(New_sj[0]==60)
{New_sj[0]=0;}
while(!k3);
}
}
if(k4==0) //-
{
Delay10ms();
if(k4==0)
{
if(New_sj[0]==0)
{New_sj[0]=60;}
New_sj[0]--;
while(!k4);
}
}
}
/***********Set_NZ************/
//////////////////////////
if(Set_NZ==1)//Hour
{
if(k3==0) //+
{
Delay10ms();
if(k3==0)
{
NZ_sj[2]++;
if(NZ_sj[2]==24)
{NZ_sj[2]=0;}
while(!k3);
}
}
if(k4==0) //-
{
Delay10ms();
if(k4==0)
{
if(NZ_sj[2]==0)
{NZ_sj[2]=24;}
NZ_sj[2]--;
while(!k4);
}
}
}
//////////////////////////
if(Set_NZ==2)
{
if(k3==0) //+
{
Delay10ms();
if(k3==0)
{
NZ_sj[1]++;
if(NZ_sj[1]==60)
{NZ_sj[1]=0;}
while(!k3);
}
}
if(k4==0) //-
{
Delay10ms();
if(k4==0)
{
if(NZ_sj[1]==0)
{NZ_sj[1]=60;}
NZ_sj[1]--;
while(!k4);
}
}
}
//////////////////////////
if(Set_NZ==3)
{
if(k3==0) //+
{
Delay10ms();
if(k3==0)
{
NZ_sj[0]++;
if(NZ_sj[0]==60)
{NZ_sj[0]=0;}
while(!k3);
}
}
if(k4==0) //-
{
Delay10ms();
if(k4==0)
{
if(NZ_sj[0]==0)
{NZ_sj[0]=60;}
NZ_sj[0]--;
while(!k4);
}
}
}
}
ds1302.c
#include "ds1302.h"
unsigned char sj[7]; //读取时间缓存区域
unsigned char tabsj[]={1, 20, 20, 18, 6, 2, 24};
//秒 分 时 日 月 星期 年
/********/
void Write_Ds1302_Byte(unsigned char temp)
{
unsigned char i;
for (i=0;i<8;i++)
{
SCK=0;
SDA=temp&0x01;
temp>>=1;
SCK=1;
}
}
void Write_Ds1302( unsigned char address,unsigned char dat )
{
unsigned char aum;
RST=0;
_nop_();
SCK=0;
_nop_();
RST=1;
_nop_();
Write_Ds1302_Byte(address);
aum=(dat/10<<4)|(dat%10);
Write_Ds1302_Byte(aum);
RST=0;
}
/***新加时间初始化***/
void DS1302_init()
{
unsigned char i;
unsigned char add=0x80; //定义初始写入地址
Write_Ds1302(0x8e,0x00); //对WP置零,允许写操作
for(i=0;i<7;i++) //循环7次依次
{ //
Write_Ds1302(add,tabsj[i]); //初始化 秒 分 时 日 月 星期 年
add=add+2; //变更写入地址
}
Write_Ds1302(0x8e,0x80); //开启写保护
}
unsigned char Read_Ds1302 ( unsigned char address )
{
unsigned char i,temp=0x00;
unsigned char dat1,dat2;
RST=0;
_nop_();
SCK=0;
_nop_();
RST=1;
_nop_();
Write_Ds1302_Byte(address);
for (i=0;i<8;i++)
{
SCK=0;
temp>>=1;
if(SDA)
temp|=0x80;
SCK=1;
}
RST=0;
_nop_();
RST=0;
SCK=0;
_nop_();
SCK=1;
_nop_();
SDA=0;
_nop_();
SDA=1;
dat1=temp/16; //转为16进制
dat2=temp%16;
temp=dat1*10+dat2;
return (temp);
}
void readtime()
{
unsigned char i;
unsigned char add=0x81; //定义初始读取地址
Write_Ds1302(0x8e,0x00); //关闭写保护
for(i=0;i<7;i++)
{
sj[i]=Read_Ds1302(add); //将读出地址依次存入缓存区域
add=add+2; //变更读取地址
}
Write_Ds1302(0x8e,0x80); //开启写保护
}
void Ds1302_Stop()
{
unsigned char temp;
Write_Ds1302(0x8e,0x00);
temp=Read_Ds1302(0x81);
Write_Ds1302(0x80,temp|0x80);
Write_Ds1302(0x8e,0x80);
}
void Ds1302_Start()
{
unsigned char temp;
Write_Ds1302(0x8e,0x00);
temp=Read_Ds1302(0x81);
Write_Ds1302(0x80,temp&0x7f);
Write_Ds1302(0x8e,0x80);
}
感谢点赞支持,关注后续分享更多实用案例
更多推荐



所有评论(0)