1、基于单片机的多功能LCD万年历时钟设计与温度显示系统
点击链接下载prrotues仿真设计资料:https://download.csdn.net/download/m0_51061483/92081498
1.1、系统概述
本设计以单片机为控制核心,构建一套集"万年历时钟显示、时间校准与设置、温度采集与显示、人机按键交互"为一体的多功能LCD万年历时钟系统。系统能够在LCD屏幕上实时显示完整的年月日时分秒信息,并支持闰年判断、月份天数自动切换等万年历功能。同时,系统通过DS18B20数字温度传感器采集环境温度,实现温度的实时显示。为了满足实际使用需求,系统配备按键操作,可用于时间的修改、参数设置与校准,确保时钟在长期运行中仍能保持准确性和良好的可用性。
在硬件结构上,系统主要由单片机最小系统模块、LCD显示模块、时钟计时模块(可采用DS1302/DS3231或软件计时)、DS18B20温度采集模块、按键输入模块、电源模块、蜂鸣器或提示模块(可扩展)等构成。
在软件结构上,系统采用模块化设计思想,将显示刷新、按键扫描、时间计时、万年历计算、温度读取、设置界面逻辑等分别封装为独立模块,并通过定时中断产生系统节拍,实现稳定、实时、低延迟的运行效果。
系统适用于桌面电子钟、学习型嵌入式作品、智能家居显示终端等场景。通过本设计,能够掌握单片机基础外设驱动、LCD显示、1-Wire总线通信、时间与日期算法、按键消抖与交互状态机设计等关键知识点,具有较强的综合实践意义。
2、系统功能设计
2.1、LCD显示完整时间信息(年月日时分秒)
系统能够在LCD屏幕上显示当前时间的完整信息,包括:
- 年:YYYY
- 月:MM
- 日:DD
- 时:HH(24小时制)
- 分:MM
- 秒:SS
典型显示形式可为两行显示:
- 第一行:
2026-01-05 - 第二行:
12:34:56 25.3C
其中第一行主要显示日期信息,第二行显示时间与温度信息。通过合理布局,用户能够在同一屏幕中获得日期、时间与温度的综合信息。
为了保证显示观感良好,系统在显示时应考虑:
- 数字补零(如 01 月、09 分)
- 固定位置刷新(避免屏幕闪烁)
- 采用缓存字符串刷新方式(先拼接字符串再输出)
- 时间变化频率为1秒刷新,温度可每500ms或1s刷新一次
2.2、支持时间校准功能,确保时间准确
系统提供时间校准功能,通过按键操作进入设置模式,对时间进行手动调整。校准对象包括:
- 年
- 月
- 日
- 时
- 分
- 秒
时间校准不仅用于初次上电设定,也用于长期运行后因晶振误差导致的时间偏差修正。对于使用软件计时的方案,系统可以采用定时器中断每秒累加时间;对于使用硬件RTC(如DS1302/DS3231)的方案,则可通过串行接口读写时钟芯片寄存器实现更高精度的计时。
在本设计中,为保证功能完整并便于扩展,推荐采用"外接RTC + 单片机显示与管理"结构,能够有效减轻单片机计时误差问题,同时在掉电后保持时间不丢失。
2.3、DS18B20采集温度并实时显示
系统通过DS18B20温度传感器读取环境温度,并将温度值转换成可读格式显示在LCD上。DS18B20具备如下优点:
- 数字输出,抗干扰能力强
- 采用1-Wire单总线,节省IO口
- 分辨率可配置(9~12位)
- 温度范围广(-55℃到+125℃)
- 精度较高(典型±0.5℃)
系统读取温度的基本流程为:
1)初始化1-Wire总线
2)发送温度转换命令(Convert T)
3)等待转换完成(典型750ms@12位)
4)读取温度寄存器(Scratchpad)
5)转换成摄氏温度并显示
为了提升体验,系统可采用"温度数据缓存 + 周期更新"的策略:
- 每秒触发一次温度读取任务
- 将温度值保存为整数或带小数形式
- LCD刷新时直接读取缓存温度值
这样可以避免频繁读取导致的延迟或影响主循环实时性。
2.4、按键操作实现时间修改与设置
按键交互是本系统的重要组成部分,它决定了系统的可操作性和用户体验。按键功能通常包括:
- 模式键(MODE):进入/退出设置模式
- 切换键(NEXT):在年/月/日/时/分/秒之间切换
- 增加键(UP):数值加1或快速增加
- 减少键(DOWN):数值减1或快速减少
- 确认键(OK):保存并退出(可选)
系统在按键逻辑中需要重点解决的问题:
- 软件消抖(避免一次按键产生多次触发)
- 长按加速(提高设置效率)
- 参数边界与合法性检查(如月份1~12、日期依据月份变化)
- 设置过程的显示提示(闪烁当前字段、显示"SET"标志等)
良好的按键交互应做到:
- 进入设置后能清晰知道当前调整的是哪个字段
- 调整后的时间合法且不出现错误日期
- 保存退出时立刻生效并继续计时
3、电路设计
3.1、硬件总体结构说明
系统硬件模块可划分为:
1)单片机最小系统模块
2)电源模块
3)LCD显示模块(LCD1602或LCD12864等)
4)时钟计时模块(RTC:DS1302/DS3231 或软件计时)
5)温度采集模块(DS18B20)
6)按键输入模块
7)蜂鸣器/提示模块(可选扩展)
本设计可选择LCD1602作为显示器件,以字符显示形式实现日期、时间和温度显示。单片机可使用STC89C52/AT89S52等51内核芯片,也可使用AVR或STM8等,本文以经典51单片机为设计基准进行描述,便于教学与实现。
3.2、单片机最小系统模块
单片机最小系统包含单片机、晶振电路、复位电路以及必要的电源去耦电容。其作用是为整个系统提供稳定运行的控制核心。
3.2.1、时钟晶振电路
晶振为单片机提供系统时钟,常用频率包括:
- 11.0592MHz:利于串口通信波特率整除
- 12MHz:传统经典频率,便于定时计算
晶振两端一般连接两个负载电容(通常20pF~33pF),形成稳定振荡。晶振布局要尽量靠近单片机时钟引脚,走线短且远离强干扰源,以保证振荡稳定。
3.2.2、复位电路
复位电路用于上电复位与按键复位,常用RC上电复位方式:
- 上电瞬间电容充电形成复位高电平
- 延迟一定时间后复位信号释放
同时可增加手动复位按键,在系统运行异常时可人为触发复位。
3.2.3、电源去耦与稳定性设计
单片机VCC与GND之间应并联0.1uF去耦电容,并尽量靠近芯片电源引脚,以滤除高频噪声。对时钟芯片、LCD、温度传感器等也应配置适当滤波电容,形成整体稳定的电源网络。
3.3、电源模块
本系统通常采用5V供电。电源来源可为:
- USB供电(5V)
- 9V~12V适配器输入后稳压到5V
- 电池供电(需升压/稳压)
稳压方案:
- 线性稳压7805:电路简单但发热较大
- DC-DC降压模块:效率高,适合长期运行
电源模块设计应考虑:
- 输入保护(防反接、保险丝)
- 输出滤波(电解电容 + 陶瓷电容)
- LCD背光电流较大,供电线需足够
- RTC与DS18B20对电源纹波敏感,建议靠近器件加小电容滤波
如果采用RTC芯片,通常还需要纽扣电池备份电源,以实现掉电保持时间功能。例如DS1302常用CR2032供电作为后备电源。
3.4、LCD显示模块
LCD作为主要人机界面,本系统可选用LCD1602或LCD12864。
- LCD1602:字符型显示,成本低,驱动简单
- LCD12864:图形点阵,可显示更多内容但驱动复杂
本设计以LCD1602为例,采用4位数据模式节省IO口。LCD1602常用引脚包括:
- RS:数据/命令选择
- RW:读写选择(通常接地仅写)
- EN:使能脉冲
- D4~D7:数据线(4位模式)
- V0:对比度调节(接电位器)
- LED+/LED-:背光供电
显示布局设计:
- 第一行显示日期:
YYYY-MM-DD - 第二行显示时间与温度:
HH:MM:SS XX.XC
为了避免闪烁,建议每秒刷新一次整个屏幕,而在秒变化时只更新秒字段亦可进一步优化显示效果。
3.5、时钟计时模块(RTC或软件计时)
万年历时钟系统最关键的模块是计时模块,它决定了时间显示是否准确、掉电是否保持。常用方案有两种:
3.5.1、方案一:外接RTC芯片(推荐)
常用RTC芯片:
- DS1302:三线接口,成本低,使用广泛
- DS3231:I2C接口,内置温补晶振,精度高
采用RTC芯片的优点:
- 计时精度高,误差小
- 掉电后可通过纽扣电池继续运行
- 单片机只需读取寄存器即可获得准确时间
- 软件实现更稳定,抗干扰更好
3.5.2、方案二:单片机软件计时
如果不使用RTC,可通过定时器中断实现秒计时:
- 定时器每1ms中断一次
- 计数到1000次为1秒
- 每秒更新时分秒,并进行日期进位与闰年判断
缺点是:
- 受晶振误差影响,长期偏差明显
- 掉电时间丢失
- 软件逻辑复杂,对中断精度要求高
本系统建议采用RTC作为计时核心,同时保留软件万年历算法用于日期合法性校验与设置时校正边界。
3.6、DS18B20温度采集模块
DS18B20采用1-Wire总线通信,仅需一根数据线即可实现数据交换。硬件连接要点:
- 数据线接单片机IO口
- 数据线上必须加上拉电阻(一般4.7kΩ)到VCC
- 传感器供电可采用外部供电模式(VDD接5V)
- 布线尽量短,避免与高干扰信号并行
DS18B20读取温度时序严格,需要软件精确延时支持。设计中要保证:
- 初始化复位脉冲时序准确
- 写0/写1、读位时序符合规范
- 读取数据后进行CRC校验(可选)提高可靠性
3.7、按键输入模块
按键模块是人机交互核心。常见硬件实现方式:
- 独立按键:每个按键占用一个IO口
- 矩阵键盘:节省IO口,但扫描复杂
对于时间设置功能,通常使用4个独立按键即可满足需求:MODE、NEXT、UP、DOWN。按键设计需要:
- 使用上拉电阻或利用单片机内部上拉
- 按键按下输出低电平或高电平,保证逻辑一致
- 软件消抖是必须的,硬件可加小电容辅助消抖(可选)
3.8、蜂鸣器或提示模块(可选扩展)
虽然题目未强制要求蜂鸣器,但为了增强交互体验,可扩展蜂鸣器作为:
- 按键提示音
- 时间设置完成提示
- 整点报时提示(扩展功能)
蜂鸣器驱动可使用有源蜂鸣器(IO直接控制)或无源蜂鸣器(PWM驱动),并通过三极管放大电流。
4、程序设计
4.1、软件总体架构与运行机制
系统软件推荐采用"主循环 + 定时中断"的结构:
- 定时器产生1ms系统节拍
- 10ms执行一次按键扫描任务
- 500ms或1s执行一次温度读取任务
- 1s执行一次时间刷新任务
- LCD周期性刷新显示内容
- 设置模式下,显示当前字段闪烁或加标识
软件模块化结构如下:
1)系统初始化模块
2)LCD驱动模块
3)RTC驱动模块(若使用DS1302/DS3231)
4)万年历算法模块(闰年判断、天数计算、日期合法性)
5)DS18B20驱动模块
6)按键扫描与设置状态机模块
7)显示格式化与刷新模块
8)可选蜂鸣器提示模块
通过这种结构,系统具有较好的扩展性和可维护性,后续可加入整点报时、闹钟、星期显示等功能而不破坏原有框架。
4.2、系统初始化模块
系统初始化主要任务:
- IO口方向配置
- 定时器初始化(1ms中断)
- LCD初始化并清屏
- RTC初始化(若存在)
- DS18B20初始化(检测传感器是否存在)
- 时间变量与温度变量默认值设置
- 进入正常显示模式
初始化应尽量避免过长阻塞,尤其是LCD初始化与温度初始化需要适当延时,但延时应在可控范围内。
4.3、LCD显示模块设计
LCD显示模块负责:
- 写命令、写数据
- 指定位置显示字符串
- 数值转换为字符串(补零、插入符号)
- 刷新显示缓存
为了实现稳定显示,建议使用"显示缓冲区"方式:
1)先将日期、时间、温度拼接到两个字符串缓冲区
2)再一次性写入LCD对应行
这样可避免刷新过程中出现字符残留或显示错位。
显示内容格式建议:
- 第一行:
YYYY-MM-DD - 第二行:
HH:MM:SS XX.XC
其中温度可显示一位小数,若需要更精细可以显示两位小数,但LCD长度有限,需合理布局。
4.4、万年历算法模块设计
万年历算法的核心是日期进位和合法性判断,关键点包括:
- 闰年判断
- 每月天数计算
- 日期进位(秒→分→时→日→月→年)
- 设置时确保日期合法(例如2月不能设置30日)
4.4.1、闰年判断
闰年规则:
- 能被4整除且不能被100整除,为闰年
- 或能被400整除,为闰年
4.4.2、每月天数计算
- 1,3,5,7,8,10,12月为31天
- 4,6,9,11月为30天
- 2月闰年29天,平年28天
4.4.3、日期进位逻辑
当秒达到60,秒归0,分加1;
当分达到60,分归0,时加1;
当时达到24,时归0,日加1;
当日超过本月最大天数,日归1,月加1;
当月超过12,月归1,年加1。
该算法不仅用于正常计时,也用于设置模式下对用户输入进行约束和修正,避免无效日期导致显示错误。
4.5、DS18B20温度采集模块设计
DS18B20驱动是本系统重点模块之一,涉及严格时序控制。软件流程如下:
1)总线复位 → 检测存在脉冲
2)发送Skip ROM(0xCC)跳过ROM
3)发送Convert T(0x44)开始温度转换
4)等待转换完成(可延时或读取状态位)
5)再次复位
6)发送Skip ROM(0xCC)
7)发送Read Scratchpad(0xBE)
8)读取2字节温度值
9)按分辨率换算为摄氏温度
温度数据一般为16位有符号数,低4位为小数部分(默认12位分辨率)。换算可采用整数运算避免浮点。
例如:
- raw = temp_high<<8 | temp_low
- 实际温度(0.0625℃/LSB)
- 若要显示一位小数,可计算:temp_x10 = raw * 10 / 16
读取温度应避免过于频繁,因为转换时间较长。通常1秒读取一次即可满足"实时显示"的需求,同时避免阻塞主循环。
4.6、按键扫描与设置模块设计
按键扫描建议每10ms执行一次。消抖策略:
- 连续多次检测到同一按键按下才认为有效
- 检测到按键释放后才产生一次按键事件
设置模式逻辑可设计为状态机:
-
正常模式:显示时间与温度,按MODE进入设置
-
设置模式:
- MODE:进入/退出设置
- NEXT:选择要修改的字段(年→月→日→时→分→秒)
- UP/DOWN:修改当前字段数值
- 退出时保存到RTC或系统时间变量
为了增强提示性,设置模式下可让当前字段闪烁:
- 每500ms切换一次显示/空白,实现闪烁效果
- 或在字段前加"^"或"*"标识(LCD1602不支持直接移动光标闪烁时,可用空格覆盖实现)
数值修改时必须保证合法:
- 月范围1~12
- 日范围1~本月最大天数
- 时范围0~23
- 分秒范围0~59
如果用户把月份改为2月,则应自动限制日期最大为28/29。
4.7、时间校准与写入模块设计
若系统使用RTC芯片,则时间校准需要写入RTC寄存器。以DS1302为例:
- 写入前需关闭写保护
- 写入秒、分、时、日、月、星期、年等寄存器
- 写入完成后再开启写保护
如果采用软件计时,则需要在设置完成后直接更新系统时间变量,并从下一秒开始继续递增。
为了避免设置过程中计时跳动,通常在进入设置模式后暂停计时累加,退出设置后再恢复计时。
4.8、温度显示与单位处理模块设计
温度显示可选择:
- 摄氏度:XX.XC
- 也可扩展华氏度显示
由于LCD1602字符限制,常用显示方式为:
25.3C- 若为负温度显示:
-3.2C
温度显示涉及符号处理与数值分离:
- 若温度为负,显示负号并取绝对值
- 整数部分与小数部分分别计算并拼接
- 统一保留一位小数即可满足绝大多数显示需求
5、核心程序示例(模块化代码)
5.1、基础数据结构与全局变量
c
#include <reg52.h>
#include <string.h>
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
// LCD1602 4位模式示例
sbit LCD_RS = P2^6;
sbit LCD_RW = P2^5;
sbit LCD_EN = P2^7;
#define LCD_PORT P0
// 按键(低电平按下)
sbit KEY_MODE = P3^2;
sbit KEY_NEXT = P3^3;
sbit KEY_UP = P3^4;
sbit KEY_DN = P3^5;
// DS18B20数据线
sbit DQ = P1^0;
// 系统时间结构体
typedef struct {
u16 year;
u8 month;
u8 day;
u8 hour;
u8 min;
u8 sec;
} datetime_t;
datetime_t now;
// 温度(x10,表示一位小数)
int temp_x10 = 253; // 25.3℃
// 设置模式相关
bit set_mode = 0;
u8 set_field = 0; // 0年 1月 2日 3时 4分 5秒
// 定时标志
volatile u16 ms_tick = 0;
volatile bit flag_10ms = 0;
volatile bit flag_500ms = 0;
volatile bit flag_1000ms = 0;
volatile bit blink = 0;
5.2、定时器初始化与中断节拍模块
c
void Timer0_Init_1ms(void)
{
TMOD &= 0xF0;
TMOD |= 0x01; // 定时器0模式1
TH0 = (65536 - 1000) / 256; // 12MHz时约1ms(需按实际晶振修正)
TL0 = (65536 - 1000) % 256;
ET0 = 1;
TR0 = 1;
EA = 1;
}
void Timer0_ISR(void) interrupt 1
{
TH0 = (65536 - 1000) / 256;
TL0 = (65536 - 1000) % 256;
ms_tick++;
if(ms_tick % 10 == 0) flag_10ms = 1;
if(ms_tick % 500 == 0) { flag_500ms = 1; blink = !blink; }
if(ms_tick % 1000 == 0) flag_1000ms = 1;
}
5.3、LCD1602驱动模块(4位模式)
c
void LCD_Delay(u16 t)
{
while(t--);
}
void LCD_Write4(u8 dat)
{
LCD_PORT = (LCD_PORT & 0x0F) | (dat & 0xF0);
LCD_EN = 1;
LCD_Delay(50);
LCD_EN = 0;
}
void LCD_Cmd(u8 cmd)
{
LCD_RS = 0;
LCD_RW = 0;
LCD_Write4(cmd);
LCD_Write4(cmd << 4);
LCD_Delay(2000);
}
void LCD_Dat(u8 dat)
{
LCD_RS = 1;
LCD_RW = 0;
LCD_Write4(dat);
LCD_Write4(dat << 4);
LCD_Delay(2000);
}
void LCD_SetPos(u8 row, u8 col)
{
u8 addr = (row == 0) ? (0x80 + col) : (0xC0 + col);
LCD_Cmd(addr);
}
void LCD_Str(u8 row, u8 col, char *s)
{
LCD_SetPos(row, col);
while(*s) LCD_Dat(*s++);
}
void LCD_Init(void)
{
LCD_RS = 0;
LCD_RW = 0;
LCD_EN = 0;
LCD_Delay(20000);
LCD_Write4(0x30);
LCD_Delay(5000);
LCD_Write4(0x30);
LCD_Delay(200);
LCD_Write4(0x30);
LCD_Delay(200);
LCD_Write4(0x20); // 4位模式
LCD_Cmd(0x28); // 2行 5x7
LCD_Cmd(0x0C); // 开显示
LCD_Cmd(0x06); // 光标右移
LCD_Cmd(0x01); // 清屏
}
5.4、万年历算法模块(闰年与天数)
c
bit IsLeapYear(u16 y)
{
if((y % 400) == 0) return 1;
if((y % 100) == 0) return 0;
if((y % 4) == 0) return 1;
return 0;
}
u8 DaysInMonth(u16 y, u8 m)
{
switch(m)
{
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(y) ? 29 : 28;
default:
return 30;
}
}
void Time_Tick1s(datetime_t *t)
{
if(++t->sec >= 60) { t->sec = 0; if(++t->min >= 60) { t->min = 0; if(++t->hour >= 24) {
t->hour = 0;
if(++t->day > DaysInMonth(t->year, t->month))
{
t->day = 1;
if(++t->month > 12)
{
t->month = 1;
t->year++;
}
}
}}}}
}
5.5、DS18B20驱动模块(1-Wire时序)
c
void DelayUs(u16 us)
{
while(us--);
}
bit DS18B20_Reset(void)
{
bit presence;
DQ = 1;
DelayUs(5);
DQ = 0; // 拉低480us
DelayUs(500);
DQ = 1; // 释放
DelayUs(70);
presence = DQ; // 读取存在脉冲(0表示存在)
DelayUs(410);
return (presence == 0);
}
void DS18B20_WriteBit(bit b)
{
DQ = 0;
DelayUs(b ? 5 : 60);
DQ = 1;
DelayUs(b ? 60 : 5);
}
bit DS18B20_ReadBit(void)
{
bit b;
DQ = 0;
DelayUs(5);
DQ = 1;
DelayUs(5);
b = DQ;
DelayUs(60);
return b;
}
void DS18B20_WriteByte(u8 dat)
{
u8 i;
for(i=0; i<8; i++)
{
DS18B20_WriteBit(dat & 0x01);
dat >>= 1;
}
}
u8 DS18B20_ReadByte(void)
{
u8 i, dat = 0;
for(i=0; i<8; i++)
{
dat >>= 1;
if(DS18B20_ReadBit()) dat |= 0x80;
}
return dat;
}
int DS18B20_ReadTemp_x10(void)
{
u8 tl, th;
int raw;
int t10;
if(!DS18B20_Reset()) return 9999; // 传感器异常
DS18B20_WriteByte(0xCC); // Skip ROM
DS18B20_WriteByte(0x44); // Convert T
// 等待转换(12位750ms),这里简单延时
// 实际可根据分辨率优化或用读取状态方式减少等待
DelayUs(750000);
if(!DS18B20_Reset()) return 9999;
DS18B20_WriteByte(0xCC);
DS18B20_WriteByte(0xBE); // Read Scratchpad
tl = DS18B20_ReadByte();
th = DS18B20_ReadByte();
raw = (th << 8) | tl; // 16位有符号
// raw单位为1/16℃
// 转为x10:raw * 10 / 16
t10 = (raw * 10) / 16;
return t10;
}
5.6、按键扫描与设置状态机模块
c
u8 Key_ReadRaw(void)
{
if(KEY_MODE == 0) return 1;
if(KEY_NEXT == 0) return 2;
if(KEY_UP == 0) return 3;
if(KEY_DN == 0) return 4;
return 0;
}
u8 Key_Scan(void)
{
static u8 last = 0;
static u8 cnt = 0;
u8 now = Key_ReadRaw();
if(now == last)
{
if(cnt < 3) cnt++;
}
else
{
cnt = 0;
}
last = now;
if(cnt == 3 && now != 0)
{
while(Key_ReadRaw() == now); // 等待松手
return now;
}
return 0;
}
void FixDateIfNeeded(datetime_t *t)
{
u8 maxd = DaysInMonth(t->year, t->month);
if(t->day > maxd) t->day = maxd;
if(t->day < 1) t->day = 1;
}
void Set_ProcessKey(u8 k)
{
switch(k)
{
case 1: // MODE:退出设置
set_mode = 0;
set_field = 0;
break;
case 2: // NEXT:切换字段
set_field++;
if(set_field > 5) set_field = 0;
break;
case 3: // UP:增加
if(set_field == 0) now.year++;
else if(set_field == 1) { if(++now.month > 12) now.month = 1; FixDateIfNeeded(&now); }
else if(set_field == 2) { if(++now.day > DaysInMonth(now.year, now.month)) now.day = 1; }
else if(set_field == 3) { if(++now.hour > 23) now.hour = 0; }
else if(set_field == 4) { if(++now.min > 59) now.min = 0; }
else if(set_field == 5) { if(++now.sec > 59) now.sec = 0; }
break;
case 4: // DOWN:减少
if(set_field == 0) { if(now.year > 2000) now.year--; }
else if(set_field == 1) { if(now.month <= 1) now.month = 12; else now.month--; FixDateIfNeeded(&now); }
else if(set_field == 2) { if(now.day <= 1) now.day = DaysInMonth(now.year, now.month); else now.day--; }
else if(set_field == 3) { if(now.hour == 0) now.hour = 23; else now.hour--; }
else if(set_field == 4) { if(now.min == 0) now.min = 59; else now.min--; }
else if(set_field == 5) { if(now.sec == 0) now.sec = 59; else now.sec--; }
break;
}
}
void Key_Process(void)
{
u8 k = Key_Scan();
if(k == 0) return;
if(!set_mode)
{
if(k == 1) { set_mode = 1; set_field = 0; }
}
else
{
Set_ProcessKey(k);
}
}
5.7、显示格式化与刷新模块
c
void TwoDigit(u8 v, char *buf)
{
buf[0] = (v / 10) + '0';
buf[1] = (v % 10) + '0';
}
void FourDigit(u16 v, char *buf)
{
buf[0] = (v / 1000) % 10 + '0';
buf[1] = (v / 100) % 10 + '0';
buf[2] = (v / 10) % 10 + '0';
buf[3] = (v % 10) + '0';
}
void TempToStr(int t10, char *buf)
{
// 格式:-XX.XC 或 XX.XC
int abs = t10;
u8 neg = 0;
if(t10 < 0) { neg = 1; abs = -t10; }
buf[0] = neg ? '-' : ' ';
buf[1] = (abs / 100) % 10 + '0';
buf[2] = (abs / 10) % 10 + '0';
buf[3] = '.';
buf[4] = (abs % 10) + '0';
buf[5] = 'C';
buf[6] = '\0';
}
void LCD_Refresh(void)
{
char line1[17] = "0000-00-00 ";
char line2[17] = "00:00:00 00.0C ";
char buf[8];
// 日期行
FourDigit(now.year, buf);
memcpy(&line1[0], buf, 4);
TwoDigit(now.month, buf);
memcpy(&line1[5], buf, 2);
TwoDigit(now.day, buf);
memcpy(&line1[8], buf, 2);
// 时间行
TwoDigit(now.hour, buf);
memcpy(&line2[0], buf, 2);
TwoDigit(now.min, buf);
memcpy(&line2[3], buf, 2);
TwoDigit(now.sec, buf);
memcpy(&line2[6], buf, 2);
// 温度
TempToStr(temp_x10, buf);
memcpy(&line2[9], buf, 6);
// 设置模式闪烁当前字段(简单示例:500ms隐藏字段)
if(set_mode && blink)
{
switch(set_field)
{
case 0: memcpy(&line1[0], " ", 4); break; // 年
case 1: memcpy(&line1[5], " ", 2); break; // 月
case 2: memcpy(&line1[8], " ", 2); break; // 日
case 3: memcpy(&line2[0], " ", 2); break; // 时
case 4: memcpy(&line2[3], " ", 2); break; // 分
case 5: memcpy(&line2[6], " ", 2); break; // 秒
}
}
LCD_Str(0, 0, line1);
LCD_Str(1, 0, line2);
}
5.8、主程序框架
c
void System_Init(void)
{
// 默认时间(可在此从RTC读取)
now.year = 2026;
now.month = 1;
now.day = 5;
now.hour = 12;
now.min = 0;
now.sec = 0;
LCD_Init();
Timer0_Init_1ms();
}
void main(void)
{
System_Init();
while(1)
{
if(flag_10ms)
{
flag_10ms = 0;
Key_Process();
}
if(flag_1000ms)
{
flag_1000ms = 0;
// 非设置模式下正常走时
if(!set_mode)
Time_Tick1s(&now);
// 每秒读取一次温度(可优化为2秒一次)
temp_x10 = DS18B20_ReadTemp_x10();
}
if(flag_500ms)
{
flag_500ms = 0;
LCD_Refresh();
}
}
}
6、系统可靠性与优化建议
6.1、提升时间准确性与掉电保持
若系统使用软件计时,长期运行会产生误差。推荐使用RTC芯片,尤其是DS3231,它采用温补晶振,日误差极小,适合长期高精度计时应用。配合纽扣电池,可实现断电时间不丢失。
6.2、温度采样的非阻塞优化
DS18B20在12位分辨率下温度转换时间最长可达750ms,若采用阻塞延时会降低系统响应。可优化为:
- 在任务调度中先发起Convert T命令
- 后续周期再读取Scratchpad
- 采用状态机实现非阻塞采集
这样可以使按键响应更灵敏,显示刷新更流畅。
6.3、按键长按加速与体验提升
在时间设置时,逐次按键加减效率较低。可加入长按加速:
- 长按500ms后开始连续加减
- 连续加减速度逐渐加快(例如每100ms变化一次)
提高时间设置效率,使系统更接近实际电子钟体验。
6.4、显示布局扩展与信息丰富
在LCD1602限制下仍可扩展显示:
- 星期显示(Mon/Tue或数字1~7)
- 设置模式显示"SET"标识
- 温度异常显示"Err"提示
若采用LCD12864,可进一步显示更丰富界面,如图标、曲线、菜单等。
7、总结
基于单片机的多功能LCD万年历时钟设计与温度显示系统,将时间计时、万年历算法、LCD显示、DS18B20温度采集与按键交互等功能有机结合,实现了"年月日时分秒完整显示、时间校准设置、实时温度显示、按键修改参数"等核心需求。硬件部分由单片机最小系统、电源模块、LCD显示模块、时钟计时模块、DS18B20温度采集模块与按键模块构成,各模块功能清晰、接口简单、易于实现与扩展。软件部分采用模块化思想与定时任务机制,实现了稳定可靠的显示刷新、按键消抖、万年历进位与合法性判断、温度读取与格式化显示、设置模式状态机等关键功能。
该系统不仅能够满足日常电子时钟与环境温度显示需求,同时也具备良好的扩展空间。后续可进一步增加闹钟、整点报时、温度报警、串口上位机显示、自动校时等功能,使其成为更完善的智能时间与环境信息显示终端。