一、设计概述


本设计以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);
}


 感谢点赞支持,关注后续分享更多实用案例

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐