基于51单片机的电子钟设计(LCD12864显示 + DS1302)

目录

一、设计背景

二、实现功能

[(1)LCD12864 汉字显示](#(1)LCD12864 汉字显示)

[(2)DS1302 实时计时](#(2)DS1302 实时计时)

(3)主界面显示

[(4)双闹钟 + 4种触发模式](#(4)双闹钟 + 4种触发模式)

(5)按键设置功能

[(6)蜂鸣器间歇鸣叫 + 贪睡功能](#(6)蜂鸣器间歇鸣叫 + 贪睡功能)

(7)Timer0定时器调度

三、仿真结果

四、源程序

五、总结

六、相关资料获取


一、设计背景

在日常生活和学习中,电子钟是最常见的时间管理工具。传统的数字钟多采用数码管显示,只能显示简单的数字,无法显示汉字和复杂信息。而 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

相关推荐
半条-咸鱼2 小时前
【FreeRTOS】核心原理与实战速查手册
c语言·操作系统·rtos
执迷不悟8232 小时前
51单片机--通信/串口
单片机·嵌入式硬件·51单片机
白帽小阳2 小时前
Typora插件开发指南:打造专属IDE式写作环境
c语言·网络·python·网络安全·github·pygame·护网行动
ysu_031412 小时前
06 | 200个单元测试C项目也能TDD
c语言·单元测试·tdd
碎光拾影15 小时前
CPU与MCU核心区别揭秘
单片机·学习·51单片机
烟锁池塘柳021 小时前
【C/C++】解决C++控制台输出中文乱码问题
c语言·开发语言·c++
捷瑞电子工坊1 天前
C语言联合体(Union)——让同一块内存“变脸”
c语言·union·联合体·内存共享
aaPIXa6221 天前
C++ 用 Claude Code 的 defending-code-reference-harness:C/C++ 内存漏洞自动发现与修复 Pipeline
java·c语言·c++
xingke2 天前
C 语言多文件编程:(一)头文件、extern、编译链接
c语言·开发语言·多文件