扫码加入

  • 方案介绍
  • 相关推荐
申请入驻 产业图谱

基于单片机的多功能LCD万年历时钟设计与温度显示系统

4小时前
47
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

1 基于单片机的多功能LCD万年历时钟设计与温度显示系统

点击链接下载protues仿真设计资料:https://download.csdn.net/download/m0_51061483/92081498

1.1 设计背景与意义

在日常生活、工业设备、人机交互终端以及教学实验中,时间与温度信息属于最常见、最基础的显示需求之一。传统的时钟装置通常只显示时分秒,而在实际应用中,人们更需要完整的日期信息(年、月、日、星期)以及能够进行校准和设置的功能。同时,环境温度的实时监测也在智能家居、仪表设备、实验室管理等领域具有重要价值。

基于单片机的LCD万年历时钟系统可以把时间、日期、温度等信息集成到一个显示终端中,并通过按键进行设置校准,具有结构清晰、成本低、可扩展性强、学习价值高等特点。尤其是在课程设计或仿真设计中,该系统能够综合训练以下能力:
1)单片机外设驱动与模块化编程思想;
2)LCD字符显示控制与界面布局;
3)实时时钟(RTC)芯片通信与时间数据管理;
4)温度传感器采集与数据转换显示;
5)按键去抖、状态机控制与参数设置流程设计;
6)软件与硬件协同调试、系统鲁棒性设计。

因此,本课题设计一套“基于单片机的多功能LCD万年历时钟设计与温度显示系统”,实现年月日时分秒完整显示、时间校准设置、DS18B20温度采集显示以及按键修改设置等功能,具备较强的实用性与工程训练价值。


2 系统功能与总体方案

2.1 系统功能概述

系统需要实现以下核心功能:

  1. LCD显示年月日时分秒完整时间信息,界面清晰直观。
  2. 支持时间校准功能,可对年、月、日、时、分、秒逐项设置,确保时间准确。
  3. 通过DS18B20温度传感器采集环境温度,并实时显示在LCD屏幕上。
  4. 按键操作实现时间修改与设置,包含模式切换、数值增减、确认保存等功能。

在工程层面,为了让系统稳定易用,还应具备以下辅助特性:
1)上电初始化显示欢迎界面或默认界面;
2)时间显示采用固定格式,保证数值对齐;
3)按键具备去抖与长按加速逻辑,提高操作体验;
4)温度显示支持负温度,并显示小数(DS18B20默认分辨率为0.0625℃);
5)系统定时刷新机制合理,不出现屏幕闪烁或按键迟滞。


2.2 系统总体结构

系统由以下模块组成:

  1. 单片机最小系统模块(51单片机或其他常见MCU
  2. 实时时钟模块(RTC芯片,如DS1302/DS1307等)
  3. LCD显示模块(常用1602字符LCD或12864点阵LCD)
  4. 温度采集模块(DS18B20数字温度传感器
  5. 按键输入模块(模式键、加键、减键、确认键等)
  6. 电源模块(+5V供电及滤波)
  7. 备用电池模块(RTC供电,保证断电走时)

系统工作流程可概括为:
1)RTC芯片持续计时并保存当前时间;
2)单片机周期读取RTC时间,并更新LCD显示;
3)单片机周期读取DS18B20温度数据并显示;
4)按键触发进入设置模式,单片机修改时间变量并写入RTC实现校准;
5)系统始终保持稳定刷新与实时响应,确保显示准确与操作便捷。


3 硬件电路设计

3.1 硬件设计总体原则

本系统硬件设计应遵循:

  1. 可靠性优先:RTC和DS18B20对时序要求较高,应保证信号稳定、上拉电阻合适。
  2. 模块化设计:各模块接口清晰,便于调试与扩展。
  3. 抗干扰性:按键输入、时钟通信线、温度信号线应考虑抗干扰,避免误触发或数据异常。
  4. 供电稳定:LCD、RTC与传感器需要稳定电压,必须配置去耦电容与滤波。
  5. 易于仿真与实现:采用常见器件组合,便于在PROTEUS等平台搭建或在实物中落地。

3.2 单片机最小系统模块设计

3.2.1 单片机选型说明

系统核心控制器可选择传统51单片机(如STC89C52/AT89C52),其优点是:
1)开发工具成熟,资料丰富;
2)I/O口数量足够连接LCD、RTC、DS18B20和按键;
3)适合课程设计与入门仪表项目;
4)成本低,易于仿真验证。

在本系统中,51单片机完全能够胜任:
1)周期读取RTC与DS18B20;
2)驱动LCD显示;
3)处理按键设置逻辑;
4)执行时间校准写入操作。

3.2.2 时钟电路设计

单片机常使用外部晶振(11.0592MHz或12MHz):
1)12MHz便于定时器计算;
2)11.0592MHz适合串口波特率精确。
本系统无需串口,可采用12MHz。晶振两端加22pF电容到地,保证振荡稳定。

3.2.3 复位电路设计

采用RC上电复位电路,确保上电后单片机稳定进入程序。并可加入按键复位方便调试。
复位设计建议:
1)RST引脚上拉电阻与电容形成延时;
2)按键复位时将RST直接拉高;
3)必要时在RST与地之间加小电容抑制噪声。

3.2.4 I/O口资源分配规划

典型分配方式如下:
1)LCD1602:数据线D4~D7(4位模式)+ RS、RW、EN
2)RTC模块:DS1302使用三线(CE、SCLK、IO)或DS1307使用I2C(SCL、SDA)
3)DS18B20:单线DQ
4)按键:4个或更多(模式/选择/加/减/确认)
合理规划可以减少端口冲突,并便于程序设计。


3.3 LCD显示模块设计

3.3.1 LCD类型选择与理由

常见LCD显示模块有两类:
1)LCD1602字符液晶:显示两行,每行16字符,适合显示时间日期温度等字符信息;
2)LCD12864点阵液晶:显示内容更丰富,可显示汉字与图形,但驱动复杂。

本系统核心需求是显示年月日时分秒与温度,采用LCD1602即可满足,并且驱动程序简单、资源占用少、仿真模型丰富,因此推荐LCD1602。

3.3.2 LCD1602显示内容布局设计

LCD1602只有两行显示,因此需要合理安排显示格式。典型布局:

  • 第1行:YYYY-MM-DD HH:MM(或显示星期)
  • 第2行:SS TEMP: +25.3C

也可采用:

  • 第1行:DATE:2026-01-01
  • 第2行:TIME:12:30:45 T:25.3

设计时应保证:
1)数字位数固定,用0补齐,例如09月显示为“09”;
2)符号与单位位置固定,便于观察;
3)温度显示保留1位小数即可满足日常需求。

3.3.3 LCD接口模式

LCD1602支持8位与4位两种数据模式:
1)8位模式:速度快,但占用IO多;
2)4位模式:只使用D4~D7,占用IO少,适合多模块系统。
本设计推荐使用4位模式,释放更多IO给RTC、DS18B20与按键。

3.3.4 对比度与背光电路

LCD的对比度由VO引脚控制,通常通过10k电位器调节,使字符清晰。背光LED需要限流,可直接由5V供电或由单片机控制实现背光开关(扩展功能)。


3.4 实时时钟(RTC)模块设计

3.4.1 RTC模块作用

单片机内部定时器可实现计时,但受程序运行与掉电影响,长期计时精度与断电保持能力不足。RTC芯片可以提供:
1)高精度走时(配合32.768kHz晶振);
2)断电数据保持(外接纽扣电池);
3)提供年月日时分秒数据,甚至星期信息;
4)可通过串行接口由单片机读取与校准设置。

3.4.2 RTC芯片选型

常用RTC芯片:
1)DS1302:三线串行接口,使用方便,资料丰富;
2)DS1307:I2C接口,时钟稳定;
3)DS3231:内置温补晶振,精度更高,但成本略高。

对于课程设计与仿真,DS1302最常见,因此本设计以DS1302为典型方案。DS1302输出BCD编码数据,单片机需进行BCD与十进制转换。

3.4.3 DS1302硬件接口与电路要点

DS1302引脚主要包括:
1)CE:片选使能;
2)SCLK:串行时钟;
3)I/O:数据输入输出;
4)X1/X2:外接32.768kHz晶振;
5)VCC1:备用电池输入;
6)VCC2:主电源输入。

设计要点:
1)晶振引脚走线短,避免干扰;
2)备用电池与主电源切换可靠;
3)CE与SCLK线建议加适当上拉或保持稳定输出,避免误触发;
4)数据线I/O通常加上拉电阻(如4.7k~10k),保证总线稳定。

3.4.4 RTC备用电池与断电保持

RTC模块可接CR2032纽扣电池。断电时RTC仍能持续计时,上电后单片机读取RTC即可继续显示,保证万年历时钟“不断走”。这也是万年历功能的重要基础。


3.5 DS18B20温度采集模块设计

3.5.1 DS18B20传感器特点

DS18B20是一种单总线数字温度传感器,具有以下优点:
1)测量范围宽(-55℃~+125℃);
2)精度较高(典型±0.5℃);
3)输出为数字信号,不需要ADC
4)支持多点挂载(每个传感器有唯一64位ROM码);
5)通信线仅需一根数据线DQ,大幅简化硬件连接。

本系统只需采集一只DS18B20即可满足环境温度显示需求。

3.5.2 DS18B20硬件连接与上拉电阻

DS18B20单线DQ需要外接上拉电阻(常用4.7k)到5V,用于总线空闲时保持高电平,同时保证信号上升沿。
硬件设计注意:
1)DQ线尽量短,若长线则需加强抗干扰;
2)电源端加0.1uF去耦电容;
3)可采用寄生供电模式,但推荐三线供电(VDD、GND、DQ)更稳定。

3.5.3 温度数据格式

DS18B20输出温度以16位形式存储在Scratchpad中,默认分辨率12位:

  • 每个最小单位为0.0625℃
  • 读取到的温度值需做符号扩展与小数转换
    例如:读取值为0x0191(十进制401),温度=401*0.0625=25.0625℃。
    显示时通常保留1位小数即可,如25.1℃。

3.6 按键输入模块设计

3.6.1 按键功能规划

为了实现时间校准与设置,需要设计按键操作逻辑。常用按键组合:
1)MODE键:模式切换(正常显示/设置模式)
2)SET键:选择设置项(年→月→日→时→分→秒)
3)UP键:数值增加
4)DOWN键:数值减少
5)OK键(可选):确认保存并退出设置

在按键数量有限时,也可设计为:

  • MODE:进入/退出设置
  • SET:切换字段
  • UP:加
  • DOWN:减
    这样即可完成完整设置流程。

3.6.2 按键硬件电路

按键通常采用独立按键输入:
1)按键一端接地;
2)另一端接单片机IO口,并通过上拉电阻拉到高电平;
3)按下为低电平触发。
这种方式结构简单,抗干扰能力较强。

3.6.3 按键去抖设计

按键按下与释放会产生抖动,若不去抖会导致一次按键被识别为多次。去抖可采用:
1)软件去抖:检测到按下后延时10~20ms再次确认;
2)硬件去抖:RC滤波 + 施密特触发。
本系统推荐软件去抖,简单有效,且便于实现长按加速功能。


3.7 电源模块设计

3.7.1 供电需求

本系统一般采用+5V供电,为:
1)单片机
2)LCD1602
3)DS18B20
4)DS1302主电源
提供稳定电压。RTC备用电池单独供电。

3.7.2 去耦与滤波

为了保证系统稳定:
1)单片机、RTC、DS18B20电源脚附近各放置0.1uF去耦电容;
2)电源入口放置10uF电解电容平滑波动;
3)LCD背光若由系统供电,需考虑电流并做好限流与滤波。


4 程序设计

4.1 软件总体架构设计

软件设计采用模块化结构,主要包括:

  1. 系统初始化模块:IO、定时器、LCD初始化、RTC初始化、DS18B20初始化
  2. RTC驱动模块:读取时间、写入时间、BCD转换
  3. DS18B20驱动模块:复位、读写字节、温度转换与读取
  4. LCD显示模块:字符串显示、定位显示、格式化输出
  5. 按键扫描模块:去抖、短按/长按识别
  6. 时间设置模块:设置状态机、字段切换、数值增减范围处理
  7. 主循环调度模块:定时刷新显示、定时读取温度、设置模式下的界面交互

软件设计目标:
1)正常模式下时间实时更新且显示稳定;
2)设置模式下按键操作直观,修改立即可见;
3)写入RTC后退出设置,系统继续走时;
4)温度采集不影响时间显示与按键响应。


4.2 LCD显示驱动程序设计

4.2.1 LCD初始化流程

LCD1602初始化需按标准时序执行:
1)等待LCD上电稳定(>15ms)
2)设置为4位模式
3)设置显示模式(2行显示、5x7点阵)
4)开启显示,关闭光标或闪烁
5)清屏并设置光标起始位置

4.2.2 显示刷新策略

显示刷新有两种常用方式:
1)全屏刷新:每次重新清屏并写入所有字符,简单但可能闪烁;
2)局部刷新:只更新变化字段(时间秒数、温度等),显示更稳定。
本系统推荐“局部刷新”:

  • 每秒更新一次时间显示区域;
  • 每1秒或2秒读取一次温度并更新温度区域;
  • 设置模式下实时更新被修改字段。

4.3 RTC时间读取与校准程序设计

4.3.1 RTC数据格式与转换

DS1302内部寄存器为BCD编码,如:
0x25表示十进制25。
因此读取后需要BCD转十进制:
dec = (bcd >> 4) * 10 + (bcd & 0x0F)
写入时则十进制转BCD:
bcd = (dec / 10) << 4 | (dec % 10)

4.3.2 读时间流程

1)读取秒、分、时、日、月、星期、年寄存器;
2)转为十进制存储在结构体中;
3)按显示格式输出到LCD。

4.3.3 写时间流程(时间校准)

在设置模式下修改完成后:
1)将修改后的十进制值转BCD;
2)按顺序写入DS1302对应寄存器;
3)启动RTC走时(确保CH位正确);
4)退出设置模式,恢复正常显示。


4.4 DS18B20温度采集程序设计

4.4.1 单总线通信流程

DS18B20通信包括:
1)复位脉冲(主机拉低总线480us以上)
2)等待存在脉冲(传感器回应)
3)写命令:跳过ROM(0xCC)、开始温度转换(0x44)
4)等待转换完成(最大750ms,12位分辨率)
5)再复位,写命令读取Scratchpad(0xBE)
6)读取温度低字节与高字节并合成16位数据
7)转换为实际温度并格式化显示。

4.4.2 温度显示格式处理

温度可显示为:
T:+25.3C
处理步骤:
1)判断正负
2)得到整数部分与小数部分(保留1位)
3)拼接字符串输出到LCD


4.5 按键扫描与时间设置状态机设计

4.5.1 按键扫描策略

采用10ms扫描一次按键,通过计数实现去抖:
1)检测到按键按下 → 延时确认 → 产生一次“按下事件”;
2)按键长按超过一定时间 → 进入连续加减模式(加速调节);
3)释放按键 → 停止连续加减。

4.5.2 设置模式状态机

设置流程可设计为:
1)正常显示模式:显示时间与温度
2)按MODE进入设置模式:闪烁或高亮当前字段(如年)
3)按SET切换字段:年→月→日→时→分→秒循环
4)按UP/DOWN修改当前字段数值,并进行范围限制:

  • 月:1~12
  • 日:1~28/29/30/31(按月份与闰年判断)
  • 时:0~23
  • 分秒:0~59
    5)按MODE或OK保存写入RTC并退出设置模式

4.5.3 闰年与月份天数判断

万年历系统必须正确处理闰年:
闰年规则:

  • 能被400整除是闰年;
  • 能被4整除但不能被100整除是闰年;
    否则不是闰年。
    月份天数:
  • 1,3,5,7,8,10,12为31天
  • 4,6,9,11为30天
  • 2月闰年29天,平年28天
    这样可保证日期设置合法,避免出现“2月30日”之类错误。

5 关键程序代码实现(示例)

5.1 程序说明

以下示例代码以51单片机为平台,包含LCD1602(4位模式)驱动、DS1302 RTC驱动、DS18B20温度采集、按键设置状态机与显示刷新逻辑。代码为示例结构,端口定义与延时需根据实际电路连接调整。在仿真或实物中建议分文件管理(lcd.c、ds1302.c、ds18b20.c、key.c等),便于维护。

#include <REGX52.H>
#include <stdio.h>

// -------------------- 端口定义(示例) --------------------
// LCD1602 4位模式:D4~D7连接P2.4~P2.7
sbit LCD_RS = P2^0;
sbit LCD_RW = P2^1;
sbit LCD_EN = P2^2;

#define LCD_DATA P2  // 使用P2的高四位传输数据

// DS1302
sbit DS1302_CE   = P1^0;
sbit DS1302_SCLK = P1^1;
sbit DS1302_IO   = P1^2;

// DS18B20
sbit DQ = P3^7;

// Keys
sbit KEY_MODE = P3^0;
sbit KEY_SET  = P3^1;
sbit KEY_UP   = P3^2;
sbit KEY_DOWN = P3^3;

// -------------------- 简单延时 --------------------
void DelayUs(unsigned int t) { while(t--); }
void DelayMs(unsigned int ms)
{
    unsigned int i, j;
    for(i=0;i<ms;i++)
        for(j=0;j<110;j++);
}

// -------------------- LCD驱动(4位) --------------------
void LCD_Write4Bit(unsigned char dat)
{
    // 仅写入高四位
    LCD_DATA &= 0x0F;
    LCD_DATA |= (dat & 0xF0);
    LCD_EN = 1;
    DelayUs(50);
    LCD_EN = 0;
}

void LCD_WriteCmd(unsigned char cmd)
{
    LCD_RS = 0;
    LCD_RW = 0;
    LCD_Write4Bit(cmd);
    LCD_Write4Bit(cmd<<4);
    DelayMs(2);
}

void LCD_WriteData(unsigned char dat)
{
    LCD_RS = 1;
    LCD_RW = 0;
    LCD_Write4Bit(dat);
    LCD_Write4Bit(dat<<4);
    DelayUs(50);
}

void LCD_SetCursor(unsigned char row, unsigned char col)
{
    unsigned char addr = (row==0) ? (0x80+col) : (0xC0+col);
    LCD_WriteCmd(addr);
}

void LCD_Print(char *s)
{
    while(*s) LCD_WriteData(*s++);
}

void LCD_Init(void)
{
    DelayMs(20);
    LCD_RS = 0; LCD_RW = 0; LCD_EN = 0;

    // 初始化进入4位模式
    LCD_Write4Bit(0x30); DelayMs(5);
    LCD_Write4Bit(0x30); DelayMs(5);
    LCD_Write4Bit(0x20); DelayMs(5);

    LCD_WriteCmd(0x28); // 4位,2行,5x7
    LCD_WriteCmd(0x0C); // 显示开,光标关
    LCD_WriteCmd(0x06); // 光标右移
    LCD_WriteCmd(0x01); // 清屏
    DelayMs(5);
}

// -------------------- DS1302 驱动 --------------------
unsigned char BCD2DEC(unsigned char bcd)
{
    return (bcd>>4)*10 + (bcd&0x0F);
}

unsigned char DEC2BCD(unsigned char dec)
{
    return ((dec/10)<<4) | (dec%10);
}

void DS1302_WriteByte(unsigned char dat)
{
    unsigned char i;
    for(i=0;i<8;i++)
    {
        DS1302_IO = dat & 0x01;
        DS1302_SCLK = 1;
        DelayUs(2);
        DS1302_SCLK = 0;
        dat >>= 1;
    }
}

unsigned char DS1302_ReadByte(void)
{
    unsigned char i, dat = 0;
    for(i=0;i<8;i++)
    {
        dat >>= 1;
        if(DS1302_IO) dat |= 0x80;
        DS1302_SCLK = 1;
        DelayUs(2);
        DS1302_SCLK = 0;
        DelayUs(2);
    }
    return dat;
}

void DS1302_WriteReg(unsigned char addr, unsigned char dat)
{
    DS1302_CE = 0;
    DS1302_SCLK = 0;
    DS1302_CE = 1;

    DS1302_WriteByte(addr);     // 写地址(最低位0)
    DS1302_WriteByte(dat);

    DS1302_CE = 0;
}

unsigned char DS1302_ReadReg(unsigned char addr)
{
    unsigned char dat;
    DS1302_CE = 0;
    DS1302_SCLK = 0;
    DS1302_CE = 1;

    DS1302_WriteByte(addr);     // 读地址(最低位1)
    dat = DS1302_ReadByte();

    DS1302_CE = 0;
    return dat;
}

// DS1302寄存器地址(示例)
#define DS1302_SEC   0x80
#define DS1302_MIN   0x82
#define DS1302_HOUR  0x84
#define DS1302_DATE  0x86
#define DS1302_MON   0x88
#define DS1302_DAY   0x8A
#define DS1302_YEAR  0x8C
#define DS1302_WP    0x8E

typedef struct {
    unsigned char year;
    unsigned char mon;
    unsigned char date;
    unsigned char hour;
    unsigned char min;
    unsigned char sec;
} RTC_TIME;

void DS1302_GetTime(RTC_TIME *t)
{
    t->sec  = BCD2DEC(DS1302_ReadReg(DS1302_SEC | 0x01) & 0x7F);
    t->min  = BCD2DEC(DS1302_ReadReg(DS1302_MIN | 0x01));
    t->hour = BCD2DEC(DS1302_ReadReg(DS1302_HOUR| 0x01));
    t->date = BCD2DEC(DS1302_ReadReg(DS1302_DATE| 0x01));
    t->mon  = BCD2DEC(DS1302_ReadReg(DS1302_MON | 0x01));
    t->year = BCD2DEC(DS1302_ReadReg(DS1302_YEAR| 0x01));
}

void DS1302_SetTime(RTC_TIME *t)
{
    // 关闭写保护
    DS1302_WriteReg(DS1302_WP, 0x00);

    DS1302_WriteReg(DS1302_SEC,  DEC2BCD(t->sec));
    DS1302_WriteReg(DS1302_MIN,  DEC2BCD(t->min));
    DS1302_WriteReg(DS1302_HOUR, DEC2BCD(t->hour));
    DS1302_WriteReg(DS1302_DATE, DEC2BCD(t->date));
    DS1302_WriteReg(DS1302_MON,  DEC2BCD(t->mon));
    DS1302_WriteReg(DS1302_YEAR, DEC2BCD(t->year));

    // 开启写保护
    DS1302_WriteReg(DS1302_WP, 0x80);
}

// -------------------- DS18B20 驱动(简化) --------------------
void DS18B20_Reset(void)
{
    DQ = 1;
    DelayUs(10);
    DQ = 0;
    DelayUs(500);   // 复位脉冲
    DQ = 1;
    DelayUs(60);    // 等待存在脉冲
}

bit DS18B20_Check(void)
{
    bit presence = 0;
    presence = !DQ;
    DelayUs(500);
    return presence;
}

void DS18B20_WriteBit(bit b)
{
    DQ = 0;
    DelayUs(2);
    DQ = b;
    DelayUs(60);
    DQ = 1;
}

bit DS18B20_ReadBit(void)
{
    bit b;
    DQ = 0;
    DelayUs(2);
    DQ = 1;
    DelayUs(8);
    b = DQ;
    DelayUs(50);
    return b;
}

void DS18B20_WriteByte(unsigned char dat)
{
    unsigned char i;
    for(i=0;i<8;i++)
    {
        DS18B20_WriteBit(dat & 0x01);
        dat >>= 1;
    }
}

unsigned char DS18B20_ReadByte(void)
{
    unsigned char i, dat = 0;
    for(i=0;i<8;i++)
    {
        dat >>= 1;
        if(DS18B20_ReadBit()) dat |= 0x80;
    }
    return dat;
}

int DS18B20_ReadTemp_x10(void)
{
    unsigned char LSB, MSB;
    int temp;
    long t;

    DS18B20_Reset();
    DS18B20_Check();
    DS18B20_WriteByte(0xCC); // 跳过ROM
    DS18B20_WriteByte(0x44); // 启动转换

    DelayMs(750); // 12位转换时间最大750ms

    DS18B20_Reset();
    DS18B20_Check();
    DS18B20_WriteByte(0xCC);
    DS18B20_WriteByte(0xBE); // 读Scratchpad

    LSB = DS18B20_ReadByte();
    MSB = DS18B20_ReadByte();

    temp = (MSB<<8) | LSB;

    // 转换为0.1℃单位:temp * 0.0625 * 10 = temp * 0.625
    // 用定点近似:t = temp * 625 / 1000
    t = (long)temp * 625;
    t = t / 1000;

    return (int)t; // 返回温度*10
}

// -------------------- 按键扫描(简化) --------------------
bit Key_Scan(sbit key)
{
    if(key == 0)
    {
        DelayMs(15);
        if(key == 0)
        {
            while(key == 0);
            return 1;
        }
    }
    return 0;
}

// -------------------- 日期合法性判断 --------------------
bit IsLeapYear(unsigned int year)
{
    // year为00~99,仅作为示例,可映射到2000~2099
    unsigned int y = 2000 + year;
    if((y%400)==0) return 1;
    if((y%4)==0 && (y%100)!=0) return 1;
    return 0;
}

unsigned char DaysInMonth(unsigned char year, unsigned char mon)
{
    switch(mon)
    {
        case 1: case 3: case 5: case 7: case 8: case 10: case 12:
            return 31;
        case 4: case 6: case 9: case 11:
            return 30;
        case 2:
            return IsLeapYear(year) ? 29 : 28;
        default:
            return 30;
    }
}

// -------------------- 主逻辑:显示与设置 --------------------
RTC_TIME gTime;
bit gSetMode = 0;
unsigned char gField = 0; // 0年1月2日3时4分5秒

void Display_Normal(int temp_x10)
{
    char buf[17];

    // 第1行:YYYY-MM-DD
    sprintf(buf, "20%02d-%02d-%02d", gTime.year, gTime.mon, gTime.date);
    LCD_SetCursor(0,0);
    LCD_Print(buf);

    // 第2行:HH:MM:SS T:+25.3
    sprintf(buf, "%02d:%02d:%02d ", gTime.hour, gTime.min, gTime.sec);
    LCD_SetCursor(1,0);
    LCD_Print(buf);

    // 温度显示
    {
        int t = temp_x10;
        char sign = '+';
        if(t < 0) { sign='-'; t=-t; }
        sprintf(buf, "T:%c%02d.%1dC", sign, t/10, t%10);
        LCD_SetCursor(1,9);
        LCD_Print(buf);
    }
}

void TimeField_Inc(void)
{
    unsigned char maxDay;
    switch(gField)
    {
        case 0: gTime.year = (gTime.year + 1) % 100; break;
        case 1: gTime.mon++; if(gTime.mon>12) gTime.mon=1; break;
        case 2:
            maxDay = DaysInMonth(gTime.year, gTime.mon);
            gTime.date++; if(gTime.date>maxDay) gTime.date=1;
            break;
        case 3: gTime.hour++; if(gTime.hour>23) gTime.hour=0; break;
        case 4: gTime.min++;  if(gTime.min>59)  gTime.min=0; break;
        case 5: gTime.sec++;  if(gTime.sec>59)  gTime.sec=0; break;
    }
}

void TimeField_Dec(void)
{
    unsigned char maxDay;
    switch(gField)
    {
        case 0: gTime.year = (gTime.year==0)?99:(gTime.year-1); break;
        case 1: gTime.mon  = (gTime.mon==1)?12:(gTime.mon-1); break;
        case 2:
            maxDay = DaysInMonth(gTime.year, gTime.mon);
            gTime.date = (gTime.date==1)?maxDay:(gTime.date-1);
            break;
        case 3: gTime.hour = (gTime.hour==0)?23:(gTime.hour-1); break;
        case 4: gTime.min  = (gTime.min==0)?59:(gTime.min-1); break;
        case 5: gTime.sec  = (gTime.sec==0)?59:(gTime.sec-1); break;
    }
}

void main(void)
{
    int temp_x10 = 0;
    unsigned int lastTemp = 0;
    unsigned int lastTime = 0;

    LCD_Init();
    LCD_SetCursor(0,0);
    LCD_Print("Calendar Clock");
    LCD_SetCursor(1,0);
    LCD_Print("Init...");
    DelayMs(800);
    LCD_WriteCmd(0x01);

    // 主循环
    while(1)
    {
        // 正常模式:每1秒读取时间
        if(!gSetMode)
        {
            if(lastTime++ >= 200) // 简化:假设循环约5ms,200次≈1s,实际建议用定时器
            {
                lastTime = 0;
                DS1302_GetTime(&gTime);
            }
        }

        // 温度读取:每2秒一次
        if(lastTemp++ >= 400)
        {
            lastTemp = 0;
            temp_x10 = DS18B20_ReadTemp_x10();
        }

        // 按键处理
        if(Key_Scan(KEY_MODE))
        {
            gSetMode = !gSetMode;
            if(!gSetMode)
            {
                // 退出设置,写入RTC
                DS1302_SetTime(&gTime);
            }
        }

        if(gSetMode)
        {
            if(Key_Scan(KEY_SET))
            {
                gField++;
                if(gField > 5) gField = 0;
            }
            if(Key_Scan(KEY_UP))   TimeField_Inc();
            if(Key_Scan(KEY_DOWN)) TimeField_Dec();
        }

        // 刷新显示
        Display_Normal(temp_x10);

        DelayMs(5);
    }
}

6 系统设计关键点与优化分析

6.1 时间显示稳定性与刷新策略

LCD显示若频繁清屏会闪烁,因此应避免在主循环中反复执行清屏命令。推荐采用固定位置刷新:
1)日期时间每秒刷新一次即可;
2)温度每1~2秒刷新一次即可;
3)设置模式下仅刷新变化字段,提升用户体验。
此外,显示字符串应保证长度固定,多余字符用空格覆盖,以免出现残留字符。

6.2 DS18B20采集对系统响应的影响

DS18B20转换最长需要750ms,若程序在此期间阻塞,会导致按键不灵敏、时间显示卡顿。优化方法:
1)分步执行温度转换:先发0x44启动转换,延时期间执行其他任务,之后再读取结果;
2)降低分辨率到9~11位,缩短转换时间;
3)使用定时器或任务调度实现非阻塞采集。
在简单课程设计中,直接延时读取也能运行,但提升体验建议做分步采集。

6.3 按键长按加速与设置体验优化

若要设置年份或分钟,单次加减效率较低,可加入长按加速:
1)按住UP键超过500ms开始连续增加;
2)连续增加速度由慢到快;
3)释放按键停止。
同时可在设置模式下让当前字段闪烁提示,增强交互性。例如:设置“分钟”时分钟位闪烁,提醒用户当前修改对象。

6.4 万年历合法性保障

日期设置必须判断月份天数与闰年,避免非法日期。
同时,设置月份变化时若当前日期超过新月份最大天数,应自动调整到最大天数,例如:

  • 当前3月31日,切换到4月时自动改为4月30日。
    这类细节可以显著提升系统“像真正仪表”的可靠性与专业度。

6.5 系统扩展方向

该系统具有良好扩展性,可增加:
1)星期显示(DS1302支持星期寄存器);
2)闹钟功能与蜂鸣器提示;
3)温度上下限报警;
4)背光自动控制与节能;
5)加入湿度传感器形成环境监测终端;
6)加入串口或无线通信,实现联网时钟与数据上传。


7 总结

本文设计并实现了基于单片机的多功能LCD万年历时钟与温度显示系统。系统通过RTC芯片实现年月日时分秒的完整时间管理与断电保持,通过DS18B20传感器实现环境温度采集与实时显示,并通过按键实现时间的校准、修改与设置。硬件设计采用模块化结构,电路简单可靠;软件设计采用分层驱动与状态机思想,实现显示刷新、时间读取、温度采集与按键设置的协同运行。该系统不仅具有较强的实用性,还能覆盖单片机课程设计中多个关键知识点,适合教学实验、仿真验证与实际仪表开发的基础平台。

相关推荐