目录
[(1)LCD12864 汉字显示](#(1)LCD12864 汉字显示)
[(2)DS1302 实时计时](#(2)DS1302 实时计时)
[(4)双闹钟 + 4种触发模式](#(4)双闹钟 + 4种触发模式)
[(6)蜂鸣器间歇鸣叫 + 贪睡功能](#(6)蜂鸣器间歇鸣叫 + 贪睡功能)
一、设计背景
在日常生活和学习中,电子钟是最常见的时间管理工具。传统的数字钟多采用数码管显示,只能显示简单的数字,无法显示汉字和复杂信息。而 LCD12864 液晶屏自带中文字库,能够显示汉字、字母和数字,可实现更丰富的界面信息展示。同时,DS1302 实时时钟芯片具有计时精度高、断电保持走时、接口简单等优点,是电子钟设计的经典时钟方案。
本设计以 STC89C52 单片机为控制核心,采用 LCD12864 液晶屏显示年、月、日、星期、时、分、秒等完整日期时间信息,同时支持双闹钟功能。DS1302 通过三线串行接口与单片机通信,提供精确计时。系统设置4个按键,可完成时间设置、闹钟设置、闹铃暂停/恢复等全部操作。通过C语言编写控制程序,在Proteus中进行仿真验证。
二、实现功能
管脚分配:

系统以 STC89C52 单片机为控制核心,采用 LCD12864 液晶屏(自带中文字库)显示,DS1302 实时时钟芯片计时。具体功能如下:
(1)LCD12864 汉字显示
采用 LCD12864 液晶屏(128×64点阵,自带中文字库),能够直接显示汉字、数字和英文字符,可显示4行×16字符。LCD通过8位并口与单片机P0口连接,RS/P2.6选择寄存器,RW/P2.7控制读写,EN/P3.5使能下降沿触发。系统每次写LCD时临时关闭中断(EA=0),防止时序被打断导致显示乱码。
(2)DS1302 实时计时
使用 DS1302 时钟芯片提供精确计时,外接32.768kHz晶振,通过CE/P1.0、IO/P1.7、SCLK/P3.4三线串行接口与单片机通信。DS1302内部以BCD码存储年/月/日/时/分/秒/星期,系统每秒自动读取并刷新LCD显示。外接CR2032备份电池,断电后保持走时不丢失。代码中自动完成BCD与十进制转换,以及D系统界面编码(1=周一)的转换。
(3)主界面显示
主界面分4行显示完整日期时间及双闹钟信息:

各行显示内容说明:

贪睡暂停时第4行显示 P:XX(XX为剩余贪睡秒数)。
(4)双闹钟 + 4种触发模式
系统支持两组完全独立的闹钟,可同时启用互不干扰。每组闹钟可独立设置时(0~23)、分(0~59)和触发模式,4种模式可选:

闹钟检测在每秒刷新时进行,匹配条件为"时+分"相等且秒为0的瞬间,触发精度±1秒。周循环通过位掩码实现:bit0=周一,bit1=周二,... bit6=周日。闹钟1默认每周一触发(a1wk=0x01),闹钟2默认全选(a2wk=0x7F)。
(5)按键设置功能
通过4个按键完成全部设置操作,按键功能随当前界面动态变化:

按键采用软件消抖:检测到按键后设置 kdone 标志防止重复触发,按键释放后复位标志。
时间设置流程:正常界面按K1进入设置,设置项依次为 年(2000~2099)→ 月(1~12)→ 日(1~31)→ 时(0~23)→ 分(0~59)→ 秒(0~59)→ 星期(1~7),K2加/K3减,K1切换到下一项。最后一项(星期)设置完后再按K1,系统自动将全部时间写入DS1302并返回正常界面。也可在正常界面按K4手动保存。
闹钟设置流程:正常界面按K2进入闹钟1设置(按K3进入闹钟2),设置项依次为 闹钟时(0~23)→ 闹钟分(0~59)→ 闹钟模式(off/once/daily/week),K2加/K3减,K1切换并退出。
时间设置项与范围:

(6)蜂鸣器间歇鸣叫 + 贪睡功能
闹钟触发时蜂鸣器以 500ms响 + 500ms停 的周期间歇鸣叫,由Timer0的50ms中断驱动,每10个计数(500ms)切换一次蜂鸣器状态。

(7)Timer0定时器调度
系统使用Timer0工作在16位定时模式,50ms中断一次(@12MHz),承担以下职责:

三、仿真结果
(1)正常显示,上电后LCD12864显示日期、时间、星期和双闹钟信息,DS1302时间每秒自动刷新。

(2)时间设置,按K1进入设置,K2加/K3减调整数值,K1切换设置项(年→月→日→时→分→秒→星期),设置完自动写入DS1302。

(3)闹钟设置,按K2设置闹钟1,按K3设置闹钟2。设置项:闹钟时→闹钟分→闹钟模式,K1切换并退出。


(4)闹铃触发,时间匹配时蜂鸣器500ms响/500ms停间歇鸣叫。
(5)贪睡暂停,响铃时按K4,蜂鸣器停止,显示"P:60"开始60秒倒计时。倒计时结束自动恢复鸣叫。

(6)贪睡期间再按K4,立即恢复蜂鸣器鸣叫,无需等待倒计时结束。
四、源程序
cpp
#include <reg52.h> /* 51单片机头文件 包含SFR定义 */
#include <intrins.h> /* 内部函数 _nop_() 空操作 */
//#include <STC15F2K60S2.H> /* STC扩展头文件(实物板可用P4口) */
#define uchar unsigned char /* 8位无符号字符型 0~255 */
#define uint unsigned int /* 16位无符号整型 0~65535 */
/*========== LCD12864 引脚定义 ==========*/
#define LCD_DATA P0 /* LCD数据总线 P0.0~P0.7 */
sbit LCD_RS = P2^6; /* LCD寄存器选择 0=指令 1=数据 */
sbit LCD_RW = P2^7; /* LCD读写 0=写 1=读 */
sbit LCD_EN = P3^5; /* LCD使能 下降沿锁存 */
/*========== DS1302 时钟芯片引脚 ==========*/
sbit DS_CE = P1^0; /* DS1302 片选(CE/RST) 高有效 */
sbit DS_IO = P1^7; /* DS1302 双向数据线 */
sbit DS_SCLK = P3^4; /* DS1302 串行时钟 */
/*========== 蜂鸣器 ==========*/
sbit BUZZER = P1^1; /* 蜂鸣器控制 0=响 1=停(有源蜂鸣器) */
/*========== 按键定义 低电平有效 ==========*/
sbit K1 = P2^0; /* 设置/切换键 */
sbit K2 = P2^1; /* 加键/进入闹钟1设置 */
sbit K3 = P2^2; /* 减键/进入闹钟2设置 */
sbit K4 = P2^3; /* 闹铃控制键 暂停/关闭 */
/*========== 界面状态定义 ==========*/
#define UI_NORMAL 0 /* 正常显示界面 */
#define UI_SETTIME 1 /* 设置系统时间界面 */
#define UI_SETALA1 2 /* 设置闹钟1界面 */
#define UI_SETALA2 3 /* 设置闹钟2界面 */
/*========== 闹钟模式定义 ==========*/
#define A_OFF 0 /* 关闭 不触发 */
#define A_ONCE 1 /* 单次循环 响一次后自动关闭 */
#define A_DAILY 2 /* 日循环 每天到点都响 */
#define A_WEEK 3 /* 周循环 指定星期几才响 */
/*========== 全局变量 ==========*/
uint year=2026; /* 年 */
uchar month=7; /* 月 */
uchar day=1; /* 日 */
uchar hour=22; /* 时 */
uchar min=0; /* 分 */
uchar sec=0; /* 秒 */
uchar week=4; /* 星期 1=周一 7=周日 */
uchar a1h=7, a1m=0; /* 闹钟1 时/分 */
uchar a1mode=A_DAILY; /* 闹钟1 模式 默认日循环 */
uchar a1wk=0x01; /* 闹钟1 周循环掩码 bit0=周一 */
uchar a2h=22, a2m=30; /* 闹钟2 时/分 */
uchar a2mode=A_OFF; /* 闹钟2 模式 默认关闭 */
uchar a2wk=0x7F; /* 闹钟2 周循环掩码 全选 */
uchar ui=UI_NORMAL; /* 当前界面模式 */
uchar sitem=0; /* 当前设置项编号 */
bit ring=0; /* 闹铃正在响标志 */
bit paused=0; /* 闹铃暂停标志 */
uchar pause_cnt=0; /* 暂停倒计时(秒) */
uchar beep_cnt=0; /* 蜂鸣器间歇计数(50ms为单位) */
uchar al_src=0; /* 当前响的闹钟编号 0=无 1=闹钟1 2=闹钟2 */
bit dflag=0; /* 每秒刷新标志 */
uchar t50=0; /* 50ms中断计数 20次=1秒 */
uchar last_min=0xFF; /* 上次闹钟检查的分钟 */
uchar kcnt=0; /* 按键计数(未使用) */
bit kdone=0; /* 按键已触发标志 防止重复 */
/*========== 毫秒延时函数 ==========*/
void delay_ms(uint ms) /* 粗略毫秒延时 @12MHz */
{ uint a,b; for(a=0;a<ms;a++) for(b=0;b<120;b++); }
/*========== LCD12864 驱动 (参考成功代码) ==========*/
void lcd_wc(uchar c) /* 写指令到LCD */
{
EA=0; /* 关中断 防止打断LCD时序 */
LCD_RS=0; /* RS=0 选择指令寄存器 */
LCD_RW=0; /* RW=0 写模式 */
LCD_EN=0; /* EN拉低 */
_nop_(); _nop_(); /* 等待稳定 */
LCD_DATA=c; /* 数据送总线 */
_nop_(); _nop_(); /* 等待数据稳定 */
LCD_EN=1; /* EN拉高 产生上升沿 */
_nop_(); _nop_(); /* 保持高电平 */
LCD_EN=0; /* EN拉低 下降沿锁存 */
EA=1; /* 开中断 */
}
void lcd_wd(uchar d) /* 写数据到LCD */
{
EA=0; /* 关中断 */
LCD_RS=1; /* RS=1 选择数据寄存器 */
LCD_RW=0; /* RW=0 写模式 */
LCD_EN=0; /* EN拉低 */
_nop_(); _nop_(); /* 等待稳定 */
LCD_DATA=d; /* 数据送总线 */
_nop_(); _nop_(); /* 等待数据稳定 */
LCD_EN=1; /* EN拉高 */
_nop_(); _nop_(); /* 保持 */
LCD_EN=0; /* 下降沿锁存 */
EA=1; /* 开中断 */
}
void lcd_str(uchar x, uchar y, uchar *s) /* 在指定位置写字符串 */
{
uchar addr; /* DDRAM地址 */
EA=0; /* 关中断 */
switch(y){ /* 根据行号计算基地址 */
case 0: addr=0x80+x; break; /* 第1行 起始0x80 */
case 1: addr=0x90+x; break; /* 第2行 起始0x90 */
case 2: addr=0x88+x; break; /* 第3行 起始0x88 */
default:addr=0x98+x; break; /* 第4行 起始0x98 */
}
LCD_RS=0; /* RS=0 指令模式 */
LCD_RW=0; /* RW=0 写模式 */
LCD_EN=0; /* EN拉低 */
_nop_(); _nop_(); /* 等待 */
LCD_DATA=addr; /* 写入地址 */
_nop_(); _nop_(); /* 等待 */
LCD_EN=1; /* EN拉高 */
_nop_(); _nop_(); /* 保持 */
LCD_EN=0; /* 锁存 */
EA=1; /* 开中断 */
while(*s) lcd_wd(*s++); /* 逐字符发送直到'\0' */
}
void lcd_init() /* LCD12864初始化 */
{
delay_ms(50); /* 等待LCD上电稳定 */
lcd_wc(0x30); delay_ms(5); /* 功能设定: 8位 基本指令集 */
lcd_wc(0x30); delay_ms(1); /* 再次设定 */
lcd_wc(0x0c); /* 显示开 光标关 闪烁关 */
lcd_wc(0x01); delay_ms(5); /* 清屏 */
lcd_wc(0x06); /* 入口模式: 光标自增 */
lcd_wc(0x30); /* 基本指令集 */
}
/*========== DS1302 驱动 ==========*/
void ds_wr(uchar d) /* 向DS1302写一个字节 MSB先发 */
{ uchar i; for(i=0;i<8;i++){ DS_IO=d&1; DS_SCLK=1; _nop_(); DS_SCLK=0; d>>=1; } }
/* 取最低位写到IO 时钟上升沿 右移 */
uchar ds_rd() /* 从DS1302读一个字节 LSB先读 */
{ uchar i,d=0; for(i=0;i<8;i++){ d>>=1; if(DS_IO)d|=0x80; DS_SCLK=1; _nop_(); DS_SCLK=0; _nop_(); } return d; }
/* 右移 读IO到最高位 时钟脉冲 */
void ds_w(uchar addr, uchar dat) /* 写DS1302寄存器 */
{ DS_CE=0; DS_SCLK=0; DS_CE=1; ds_wr(addr); ds_wr(dat); DS_CE=0; }
/* CE拉低初始化 CE拉高开始 写地址 写数据 CE拉低结束 */
uchar ds_r(uchar addr) /* 读DS1302寄存器 */
{ uchar d; DS_CE=0; DS_SCLK=0; DS_CE=1; ds_wr(addr|1); d=ds_rd(); DS_CE=0; return d; }
/* 地址|1=读命令 读数据 返回 */
uchar b2d(uchar b){ return (b>>4)*10+(b&0xf); } /* BCD转十进制 高4位*10+低4位 */
uchar d2b(uchar d){ return ((d/10)<<4)|(d%10); } /* 十进制转BCD 十位左移4+个位 */
void ds_init() /* DS1302初始化 */
{ DS_CE=0; DS_SCLK=0; ds_w(0x8E,0x00); if(ds_r(0x81)&0x80) ds_w(0x80,0x00); }
/* 关写保护 如果停振(CH=1)则启动 */
void ds_read() /* 读取DS1302时间到全局变量 */
{
year=2000+b2d(ds_r(0x8D)); /* 读年 0x8D */
month=b2d(ds_r(0x89)); /* 读月 0x89 */
day=b2d(ds_r(0x87)); /* 读日 0x87 */
hour=b2d(ds_r(0x85)); /* 读时 0x85 */
min=b2d(ds_r(0x83)); /* 读分 0x83 */
sec=b2d(ds_r(0x81)); /* 读秒 0x81 */
week=b2d(ds_r(0x8B)); /* 读星期 0x8B */
if(week==1) week=7; /* DS1302: 1=周日 -> 代码: 7=周日 */
else if(week>1) week--; /* DS1302: 2=周一 -> 代码: 1=周一 */
}
void ds_write() /* 把全局变量写入DS1302 */
{
ds_w(0x8E,0x00); /* 关写保护 */
ds_w(0x8C,d2b(year-2000)); /* 写年 */
ds_w(0x88,d2b(month)); /* 写月 */
ds_w(0x86,d2b(day)); /* 写日 */
ds_w(0x84,d2b(hour)); /* 写时 */
ds_w(0x82,d2b(min)); /* 写分 */
ds_w(0x80,d2b(sec)); /* 写秒 */
{ uchar w; if(week==7) w=1; else w=week+1; ds_w(0x8A,d2b(w)); } /* 写星期(反向转换) */
ds_w(0x8E,0x80); /* 开写保护 */
}
/*========== 按键扫描 ==========*/
uchar key_scan() /* 非阻塞按键扫描 */
{
uchar k=0; /* 当前键值 */
if(!K1) k=1; /* K1按下 */
else if(!K2) k=2; /* K2按下 */
else if(!K3) k=3; /* K3按下 */
else if(!K4) k=4; /* K4按下 */
if(k){ /* 有按键 */
if(!kdone){ kdone=1; return k; } /* 首次检测到 返回键值 */
} else { kdone=0; } /* 无按键 清除已触发标志 */
return 0; /* 返回无键 */
}
/*========== 显示更新 ==========*/
void disp()
{
if(ui==UI_NORMAL){ /* 正常显示界面 */
/* 第1行: 日期 d:2026-07-01 */
lcd_str(0,0,"d:"); /* 日期前缀 */
lcd_wd(year/1000+'0'); /* 年 千位 */
lcd_wd(year%1000/100+'0'); /* 年 百位 */
lcd_wd(year%100/10+'0'); /* 年 十位 */
lcd_wd(year%10+'0'); /* 年 个位 */
lcd_wd('-'); /* 分隔符 */
lcd_wd(month/10+'0'); /* 月 十位 */
lcd_wd(month%10+'0'); /* 月 个位 */
lcd_wd('-'); /* 分隔符 */
lcd_wd(day/10+'0'); /* 日 十位 */
lcd_wd(day%10+'0'); /* 日 个位 */
/* 第2行: 时间+星期 t:22:17:30 thu */
lcd_str(0,1,"t:"); /* 时间前缀 */
lcd_wd(hour/10+'0'); /* 时 十位 */
lcd_wd(hour%10+'0'); /* 时 个位 */
lcd_wd(':'); /* 冒号 */
lcd_wd(min/10+'0'); /* 分 十位 */
lcd_wd(min%10+'0'); /* 分 个位 */
lcd_wd(':'); /* 冒号 */
lcd_wd(sec/10+'0'); /* 秒 十位 */
lcd_wd(sec%10+'0'); /* 秒 个位 */
lcd_wd(' '); /* 空格分隔 */
switch(week){ /* 星期用英文缩写 逐字符写 */
case 1: lcd_str(0,1,"t:"); lcd_wd(hour/10+'0'); lcd_wd(hour%10+'0'); lcd_wd(':'); lcd_wd(min/10+'0'); lcd_wd(min%10+'0'); lcd_wd(':'); lcd_wd(sec/10+'0'); lcd_wd(sec%10+'0'); lcd_wd(' '); lcd_wd('m'); lcd_wd('o'); lcd_wd('n'); lcd_wd(' '); lcd_wd(' '); break; /* 周一 */
case 2: lcd_str(0,1,"t:"); lcd_wd(hour/10+'0'); lcd_wd(hour%10+'0'); lcd_wd(':'); lcd_wd(min/10+'0'); lcd_wd(min%10+'0'); lcd_wd(':'); lcd_wd(sec/10+'0'); lcd_wd(sec%10+'0'); lcd_wd(' '); lcd_wd('t'); lcd_wd('u'); lcd_wd('e'); lcd_wd(' '); lcd_wd(' '); break; /* 周二 */
case 3: lcd_str(0,1,"t:"); lcd_wd(hour/10+'0'); lcd_wd(hour%10+'0'); lcd_wd(':'); lcd_wd(min/10+'0'); lcd_wd(min%10+'0'); lcd_wd(':'); lcd_wd(sec/10+'0'); lcd_wd(sec%10+'0'); lcd_wd(' '); lcd_wd('w'); lcd_wd('e'); lcd_wd('d'); lcd_wd(' '); lcd_wd(' '); break; /* 周三 */
case 4: lcd_str(0,1,"t:"); lcd_wd(hour/10+'0'); lcd_wd(hour%10+'0'); lcd_wd(':'); lcd_wd(min/10+'0'); lcd_wd(min%10+'0'); lcd_wd(':'); lcd_wd(sec/10+'0'); lcd_wd(sec%10+'0'); lcd_wd(' '); lcd_wd('t'); lcd_wd('h'); lcd_wd('u'); lcd_wd(' '); lcd_wd(' '); break; /* 周四 */
case 5: lcd_str(0,1,"t:"); lcd_wd(hour/10+'0'); lcd_wd(hour%10+'0'); lcd_wd(':'); lcd_wd(min/10+'0'); lcd_wd(min%10+'0'); lcd_wd(':'); lcd_wd(sec/10+'0'); lcd_wd(sec%10+'0'); lcd_wd(' '); lcd_wd('f'); lcd_wd('r'); lcd_wd('i'); lcd_wd(' '); lcd_wd(' '); break; /* 周五 */
case 6: lcd_str(0,1,"t:"); lcd_wd(hour/10+'0'); lcd_wd(hour%10+'0'); lcd_wd(':'); lcd_wd(min/10+'0'); lcd_wd(min%10+'0'); lcd_wd(':'); lcd_wd(sec/10+'0'); lcd_wd(sec%10+'0'); lcd_wd(' '); lcd_wd('s'); lcd_wd('a'); lcd_wd('t'); lcd_wd(' '); lcd_wd(' '); break; /* 周六 */
case 7: lcd_str(0,1,"t:"); lcd_wd(hour/10+'0'); lcd_wd(hour%10+'0'); lcd_wd(':'); lcd_wd(min/10+'0'); lcd_wd(min%10+'0'); lcd_wd(':'); lcd_wd(sec/10+'0'); lcd_wd(sec%10+'0'); lcd_wd(' '); lcd_wd('s'); lcd_wd('u'); lcd_wd('n'); lcd_wd(' '); lcd_wd(' '); break; /* 周日 */
}
/* 第3行: 闹钟1 A1:07:00 daily */
lcd_str(0,2,"A1:"); /* 闹钟1前缀 */
lcd_wd(a1h/10+'0'); /* 闹钟1 时十位 */
lcd_wd(a1h%10+'0'); /* 闹钟1 时个位 */
lcd_wd(':'); /* 冒号 */
lcd_wd(a1m/10+'0'); /* 闹钟1 分十位 */
lcd_wd(a1m%10+'0'); /* 闹钟1 分个位 */
lcd_wd(' '); /* 空格 */
switch(a1mode){ /* 闹钟1 模式 逐字符写 */
case A_OFF: lcd_wd('o'); lcd_wd('f'); lcd_wd('f'); lcd_wd(' '); lcd_wd(' '); lcd_wd(' '); break; /* 关闭 */
case A_ONCE: lcd_wd('o'); lcd_wd('n'); lcd_wd('c'); lcd_wd('e'); lcd_wd(' '); lcd_wd(' '); break; /* 单次 */
case A_DAILY:lcd_wd('d'); lcd_wd('a'); lcd_wd('i'); lcd_wd('l'); lcd_wd('y'); lcd_wd(' '); break; /* 日循环 */
case A_WEEK: lcd_wd('w'); lcd_wd('e'); lcd_wd('e'); lcd_wd('k'); lcd_wd(' '); break; /* 周循环 */
}
/* 第4行: 闹钟2 A2:22:30 off */
lcd_str(0,3,"A2:"); /* 闹钟2前缀 */
lcd_wd(a2h/10+'0'); /* 闹钟2 时十位 */
lcd_wd(a2h%10+'0'); /* 闹钟2 时个位 */
lcd_wd(':'); /* 冒号 */
lcd_wd(a2m/10+'0'); /* 闹钟2 分十位 */
lcd_wd(a2m%10+'0'); /* 闹钟2 分个位 */
lcd_wd(' '); /* 空格 */
switch(a2mode){ /* 闹钟2 模式 */
case A_OFF: lcd_wd('o'); lcd_wd('f'); lcd_wd('f'); lcd_wd(' '); lcd_wd(' '); lcd_wd(' '); break; /* 关闭 */
case A_ONCE: lcd_wd('o'); lcd_wd('n'); lcd_wd('c'); lcd_wd('e'); lcd_wd(' '); lcd_wd(' '); break; /* 单次 */
case A_DAILY:lcd_wd('d'); lcd_wd('a'); lcd_wd('i'); lcd_wd('l'); lcd_wd('y'); lcd_wd(' '); break; /* 日循环 */
case A_WEEK: lcd_wd('w'); lcd_wd('e'); lcd_wd('e'); lcd_wd('k'); lcd_wd(' '); break; /* 周循环 */
}
/* 闹铃状态显示 */
if(ring){ lcd_wd(' '); lcd_wd('R'); lcd_wd('N'); lcd_wd('G'); lcd_wd(' '); } /* 正在响 显示RNG */
else if(paused){ lcd_str(0,3,"P: "); lcd_str(0,3,"P:"); lcd_wd(pause_cnt/10+'0'); lcd_wd(pause_cnt%10+'0'); } /* 暂停 显示P:XX倒计时 */
}
else if(ui==UI_SETTIME){ /* 设置时间界面 */
lcd_str(0,0,"--set time-- "); /* 第1行 标题 */
lcd_str(0,1," "); /* 第2行 先清空 */
lcd_str(0,1,"val: "); /* 第2行 值前缀 */
switch(sitem){ /* 根据设置项显示当前值 */
case 0: lcd_wd(year/1000+'0'); lcd_wd(year%1000/100+'0'); lcd_wd(year%100/10+'0'); lcd_wd(year%10+'0'); break; /* 年 */
case 1: lcd_wd(month/10+'0'); lcd_wd(month%10+'0'); break; /* 月 */
case 2: lcd_wd(day/10+'0'); lcd_wd(day%10+'0'); break; /* 日 */
case 3: lcd_wd(hour/10+'0'); lcd_wd(hour%10+'0'); break; /* 时 */
case 4: lcd_wd(min/10+'0'); lcd_wd(min%10+'0'); break; /* 分 */
case 5: lcd_wd(sec/10+'0'); lcd_wd(sec%10+'0'); break; /* 秒 */
case 6: lcd_wd(week+'0'); break; /* 星期 */
}
lcd_str(0,2,"k2+ k3- k1:next "); /* 第3行 操作提示 */
lcd_str(0,3,"hold k1 to exit "); /* 第4行 退出提示 */
}
else if(ui==UI_SETALA1 || ui==UI_SETALA2){ /* 设置闹钟界面 */
uchar ah,am,am_,aw; /* 当前闹钟参数临时变量 */
if(ui==UI_SETALA1){ ah=a1h; am=a1m; am_=a1mode; aw=a1wk; lcd_str(0,0,"--set ala1-- "); } /* 闹钟1 */
else { ah=a2h; am=a2m; am_=a2mode; aw=a2wk; lcd_str(0,0,"--set ala2-- "); } /* 闹钟2 */
lcd_str(0,1," "); /* 第2行先清空 */
lcd_str(0,1,"val: "); /* 值前缀 */
switch(sitem){ /* 根据设置项显示 */
case 0: lcd_wd(ah/10+'0'); lcd_wd(ah%10+'0'); break; /* 闹钟时 */
case 1: lcd_wd(am/10+'0'); lcd_wd(am%10+'0'); break; /* 闹钟分 */
case 2: /* 闹钟模式 */
switch(am_){
case A_OFF: lcd_str(5,1,"off"); break; /* 关闭 */
case A_ONCE: lcd_str(5,1,"once"); break; /* 单次 */
case A_DAILY:lcd_str(5,1,"daily"); break; /* 日循环 */
case A_WEEK: lcd_str(5,1,"week"); break; /* 周循环 */
} break;
}
lcd_str(0,2,"k2+ k3- k1:next "); /* 操作提示 */
lcd_str(0,3,"hold k1 to exit "); /* 退出提示 */
}
}
/*========== 闹钟检测 ==========*/
void chk_alarm()
{
uchar mask; /* 星期位掩码 */
if(ring||paused) return; /* 已在响或暂停中 不检查 */
/* 检查闹钟1 */
if(a1mode!=A_OFF && hour==a1h && min==a1m && !ring && !paused){ /* 时间匹配 */
if(a1mode==A_ONCE||a1mode==A_DAILY){ ring=1; al_src=1; } /* 单次或日循环 直接触发 */
else if(a1mode==A_WEEK){ mask=1<<(week-1); if(a1wk&mask){ ring=1; al_src=1; } } /* 周循环 检查星期掩码 */
}
/* 检查闹钟2 */
if(a2mode!=A_OFF && !ring && !paused && hour==a2h && min==a2m){ /* 时间匹配且还没响 */
if(a2mode==A_ONCE||a2mode==A_DAILY){ ring=1; al_src=2; } /* 单次或日循环 */
else if(a2mode==A_WEEK){ mask=1<<(week-1); if(a2wk&mask){ ring=1; al_src=2; } } /* 周循环 */
}
}
/*========== Timer0 初始化 50ms@12MHz ==========*/
void t0_init()
{ TMOD=(TMOD&0xF0)|0x01; TH0=0x4C; TL0=0x00; ET0=1; TR0=1; }
/* T0模式1(16位) 初值0x3CB0 允许中断 启动 */
void t0_isr() interrupt 1 /* Timer0中断 50ms触发 */
{
TH0=0x4C; /* 重装初值高字节 */
TL0=0x00; /* 重装初值低字节 */
t50++; /* 50ms计数+1 */
if(t50>=20){ /* 20次=1秒 */
t50=0; /* 清零 */
dflag=1; /* 设置刷新标志 */
if(paused){ /* 暂停中 */
if(pause_cnt>0)pause_cnt--; /* 倒计时-1 */
else { paused=0; ring=1; } /* 倒计时结束 恢复响铃 */
}
}
if(ring){ /* 闹铃正在响 */
beep_cnt++; /* 50ms计数+1 */
if(beep_cnt<10) BUZZER=0; /* 前500ms 响(低电平) */
else if(beep_cnt<20) BUZZER=1; /* 后500ms 停(高电平) */
else beep_cnt=0; /* 一个周期结束 清零 */
} else { BUZZER=1; beep_cnt=0; } /* 不响时蜂鸣器关 */
}
/*========== 主函数 ==========*/
void main()
{
uchar k; /* 按键值 */
BUZZER=1; /* 蜂鸣器关 */
ring=0; paused=0; /* 初始化闹铃状态 */
lcd_init(); /* 初始化LCD */
ds_init(); /* 初始化DS1302 */
t0_init(); /* 初始化Timer0 */
EA=1; /* 开总中断 */
ds_read(); /* 读取DS1302时间 */
disp(); /* 显示初始界面 */
while(1){ /* 主循环 */
k=key_scan(); /* 扫描按键 */
if(k){ /* 有按键 */
if(k==4&&paused){ /* 暂停中按K4: 关闭闹铃 */
paused=0; /* 取消暂停 */
BUZZER=1; /* 关蜂鸣器 */
if(al_src==1&&a1mode==A_ONCE) a1mode=A_OFF; /* 单次闹钟1自动关闭 */
if(al_src==2&&a2mode==A_ONCE) a2mode=A_OFF; /* 单次闹钟2自动关闭 */
al_src=0; /* 清除闹钟源 */
if(ui==UI_NORMAL) disp(); /* 刷新显示 */
}
else if(k==4&&ring){ /* 闹铃响时按K4: 暂停 */
ring=0; /* 停止响铃 */
paused=1; /* 标记暂停 */
pause_cnt=60; /* 60秒后恢复 */
BUZZER=1; /* 关蜂鸣器 */
if(ui==UI_NORMAL) disp(); /* 刷新显示 */
}
else if(ui==UI_NORMAL){ /* 正常界面下的按键 */
if(k==1){ ui=UI_SETTIME; sitem=0; disp(); } /* K1: 进入时间设置 */
else if(k==2){ ui=UI_SETALA1; sitem=0; disp(); } /* K2: 进入闹钟1设置 */
else if(k==3){ ui=UI_SETALA2; sitem=0; disp(); } /* K3: 进入闹钟2设置 */
else if(k==4){ ds_write(); ds_read(); disp(); } /* K4: 保存时间并刷新 */
}
else if(ui==UI_SETTIME){ /* 时间设置界面 */
if(k==1){ /* K1: 切换设置项 */
sitem++; /* 下一项 */
if(sitem>6){ ds_write(); ui=UI_NORMAL; sitem=0; } /* 超过星期 写入DS1302 退出 */
disp(); /* 刷新显示 */
}
else if(k==2){ /* K2: 加 */
switch(sitem){
case 0: year++; if(year>2099)year=2000; break; /* 年+ */
case 1: month++; if(month>12)month=1; break; /* 月+ */
case 2: day++; if(day>31)day=1; break; /* 日+ */
case 3: hour++; if(hour>23)hour=0; break; /* 时+ */
case 4: min++; if(min>59)min=0; break; /* 分+ */
case 5: sec++; if(sec>59)sec=0; break; /* 秒+ */
case 6: week++; if(week>7)week=1; break; /* 星期+ */
}
disp(); /* 刷新显示 */
}
else if(k==3){ /* K3: 减 */
switch(sitem){
case 0: year--; if(year<2000)year=2099; break; /* 年- */
case 1: month--; if(month<1)month=12; break; /* 月- */
case 2: day--; if(day<1)day=31; break; /* 日- */
case 3: hour--; if(hour>23)hour=23; break; /* 时-(uchar下溢) */
case 4: min--; if(min>59)min=59; break; /* 分- */
case 5: sec--; if(sec>59)sec=59; break; /* 秒- */
case 6: week--; if(week<1)week=7; break; /* 星期- */
}
disp(); /* 刷新显示 */
}
}
else if(ui==UI_SETALA1||ui==UI_SETALA2){ /* 闹钟设置界面 */
uchar *ah,*am,*am_; /* 指向当前闹钟变量的指针 */
if(ui==UI_SETALA1){ ah=&a1h; am=&a1m; am_=&a1mode; } /* 闹钟1 */
else { ah=&a2h; am=&a2m; am_=&a2mode; } /* 闹钟2 */
if(k==1){ /* K1: 切换设置项 */
sitem++; /* 下一项 */
if(sitem>2){ ui=UI_NORMAL; sitem=0; } /* 超过模式 退出 */
disp(); /* 刷新显示 */
}
else if(k==2){ /* K2: 加 */
switch(sitem){
case 0: (*ah)++; if(*ah>23)*ah=0; break; /* 闹钟时+ */
case 1: (*am)++; if(*am>59)*am=0; break; /* 闹钟分+ */
case 2: (*am_)++; if(*am_>3)*am_=0; break; /* 闹钟模式+ */
}
disp(); /* 刷新显示 */
}
else if(k==3){ /* K3: 减 */
switch(sitem){
case 0: (*ah)--; if(*ah>23)*ah=23; break; /* 闹钟时- */
case 1: (*am)--; if(*am>59)*am=59; break; /* 闹钟分- */
case 2: (*am_)--; if(*am_>3)*am_=3; break; /* 闹钟模式- */
}
disp(); /* 刷新显示 */
}
}
}
if(dflag){ /* 每秒刷新标志 */
dflag=0; /* 清除标志 */
if(ui==UI_NORMAL){ /* 正常界面才刷新 */
ds_read(); /* 读取DS1302最新时间 */
chk_alarm(); /* 检查闹钟触发 */
disp(); /* 刷新LCD */
}
}
}
}
五、总结
系统特点:

系统硬件清单:

该设计可作为单片机课程设计或毕业设计参考。扩展方向:增加DS18B20温度显示、增加红外遥控、增加农历显示等。
六、相关资料获取
通过百度网盘分享的文件:电子钟资料获取路径.zip
链接: https://pan.baidu.com/s/1ViV8UMXwKOBOtnf_AlY7zw?pwd=tqh8 提取码: tqh8