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 系统功能概述
系统需要实现以下核心功能:
- LCD显示年月日时分秒完整时间信息,界面清晰直观。
- 支持时间校准功能,可对年、月、日、时、分、秒逐项设置,确保时间准确。
- 通过DS18B20温度传感器采集环境温度,并实时显示在LCD屏幕上。
- 按键操作实现时间修改与设置,包含模式切换、数值增减、确认保存等功能。
在工程层面,为了让系统稳定易用,还应具备以下辅助特性:
1)上电初始化显示欢迎界面或默认界面;
2)时间显示采用固定格式,保证数值对齐;
3)按键具备去抖与长按加速逻辑,提高操作体验;
4)温度显示支持负温度,并显示小数(DS18B20默认分辨率为0.0625℃);
5)系统定时刷新机制合理,不出现屏幕闪烁或按键迟滞。
2.2 系统总体结构
系统由以下模块组成:
- 单片机最小系统模块(51单片机或其他常见MCU)
- 实时时钟模块(RTC芯片,如DS1302/DS1307等)
- LCD显示模块(常用1602字符LCD或12864点阵LCD)
- 温度采集模块(DS18B20数字温度传感器)
- 按键输入模块(模式键、加键、减键、确认键等)
- 电源模块(+5V供电及滤波)
- 备用电池模块(RTC供电,保证断电走时)
系统工作流程可概括为:
1)RTC芯片持续计时并保存当前时间;
2)单片机周期读取RTC时间,并更新LCD显示;
3)单片机周期读取DS18B20温度数据并显示;
4)按键触发进入设置模式,单片机修改时间变量并写入RTC实现校准;
5)系统始终保持稳定刷新与实时响应,确保显示准确与操作便捷。
3 硬件电路设计
3.1 硬件设计总体原则
本系统硬件设计应遵循:
- 可靠性优先:RTC和DS18B20对时序要求较高,应保证信号稳定、上拉电阻合适。
- 模块化设计:各模块接口清晰,便于调试与扩展。
- 抗干扰性:按键输入、时钟通信线、温度信号线应考虑抗干扰,避免误触发或数据异常。
- 供电稳定:LCD、RTC与传感器需要稳定电压,必须配置去耦电容与滤波。
- 易于仿真与实现:采用常见器件组合,便于在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 软件总体架构设计
软件设计采用模块化结构,主要包括:
- 系统初始化模块:IO、定时器、LCD初始化、RTC初始化、DS18B20初始化
- RTC驱动模块:读取时间、写入时间、BCD转换
- DS18B20驱动模块:复位、读写字节、温度转换与读取
- LCD显示模块:字符串显示、定位显示、格式化输出
- 按键扫描模块:去抖、短按/长按识别
- 时间设置模块:设置状态机、字段切换、数值增减范围处理
- 主循环调度模块:定时刷新显示、定时读取温度、设置模式下的界面交互
软件设计目标:
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传感器实现环境温度采集与实时显示,并通过按键实现时间的校准、修改与设置。硬件设计采用模块化结构,电路简单可靠;软件设计采用分层驱动与状态机思想,实现显示刷新、时间读取、温度采集与按键设置的协同运行。该系统不仅具有较强的实用性,还能覆盖单片机课程设计中多个关键知识点,适合教学实验、仿真验证与实际仪表开发的基础平台。
47