基于51单片机与LCD12864的电子万年历设计详解
2026/9/8 23:00:43 网站建设 项目流程

简介:这是一份基于51单片机的电子万年历完整项目资料,利用LCD12864液晶屏实时显示日期、时间及星期等信息,面向单片机初学者、电子竞赛选手及课程设计学生,解决从电路搭建到程序调试的常见难题。资源共16个文件,压缩包仅115KB,包含C语言源程序、Keil工程文件(uvproj/uvopt)、Proteus仿真电路(dsn)、可直接烧录的hex文件、编译过程文件(lst/obj/lnp)以及多个便于回溯的备份文件,覆盖代码编写、编译调试、仿真验证与烧录使用的完整链路。目前已有991人学习下载,适合课程设计、毕设参考或自学练手。该资源在源码中清晰展示了LCD12864初始化、时间基准获取、年月日时分秒转换、显示刷新等核心环节;配套的Proteus仿真可直观观察显示效果,帮助理解单片机与液晶屏的时序配合,若结合文档自行复现,可有效提升嵌入式系统设计与调试能力。 做毕业设计或者课程设计选到这个题目的朋友,我猜你现在的心情和我当年差不多——既觉得“万年历”听起来像个老古董项目,又不知道从哪儿下手。这个项目确实是单片机入门里最经典的综合训练之一,因为它把按键输入、实时时钟芯片读写、大屏点阵液晶驱动、中断定时这几大块全揉在了一起,做完这一套,51单片机算是真正入门了。这篇我就把整个“基于51单片机 + LCD12864的电子万年历”从硬件选型、原理图设计到代码框架、调试踩坑,一次性讲透。

1. 项目概述与整体设计思路

1.1 这个项目到底在做什么

电子万年历,通俗讲就是用单片机驱动一块显示屏幕,把年月日、星期、时分秒这些时间信息实时显示出来,并且支持通过按键调整时间。相比普通电子钟,万年历多出“日期、星期、农历、节气”这些扩展内容,硬件上一般需要一颗实时时钟芯片来保证掉电后时间不丢失。

用到的核心元器件就三样:单片机(一般选STC89C52或者AT89S52,都属于51系列)、时钟芯片(主流是DS1302,也有用DS3231的)、显示屏(这里是12864点阵液晶,也就是LCD12864)。这中间还牵扯到按键、蜂鸣器、晶振、电池座这些小配件。

这个项目适合谁来参考?如果你是电子信息、自动化、计算机相关专业的学生,正被课设或毕设折磨,那这篇能帮你少走很多弯路。如果你是业余爱好者,想搞明白单片机是怎么“驱动”一个屏幕的,这同样是个极好的切入点。做完这个,你基本能看懂大多数51项目的原理图和代码框架。

1.2 为什么选“51单片机 + LCD12864”这个组合

先聊选型逻辑,单纯为了显示时间,用数码管甚至LCD1602都够,但万年历项目偏偏常用12864,理由很实在:

第一,LCD12864是128×64分辨率的点阵屏,能显示汉字和图形。万年历需要显示“星期”“农历”“农历年份”这些汉字信息,用1602虽然也能显示,但字符模组显示汉字需要自定义字库,麻烦得多。12864内置字库,直接写GB2312编码就能出汉字,驱动起来反而省事。

第二,51单片机虽然老,但资料多、教程全、库函数丰富。不管是用汇编还是C语言,网上随便一搜就是一堆现成驱动,对课设来说,这意味着“出了问题有地方查”,比用一个冷门ARM芯片省心太多。而且STC89C52在5V供电下直接兼容LCD12864的5V逻辑,不用做电平转换,硬件设计简单。

第三,这个组合对初学者友好。51单片机主频低(12MHz晶振下典型1T周期约1微秒),跑LCD12864这种慢速外设刚好合适。12864的典型读写时序在微秒级,51的IO翻转速度完全跟得上,不需要插入等待周期,代码写起来和硬件配合得很顺手。

顺便说一句,有人会纠结“这都什么年代了还学51”。这里我不抬杠,但做课设、打基础、理解寄存器操作、理解时序图,51依然是最好的老师。等你用51把DS1302的时序“抠”明白了,以后上手STM32、ESP32的I2C、SPI外设会顺利得多。

1.3 方案选型的几条路线对比

我见过不少同学在选型上纠结,这里直接列个表,说清楚我为什么最终推荐“经典方案”。

方案显示器件时钟方案优点缺点适用场景
ALCD1602DS1302成本极低、教程多显示中文麻烦、屏幕小预算紧张的课设
BLCD12864DS1302中文显示方便、界面大气接线多、驱动略复杂本项目的推荐组合
CLCD12864DS3231时间精度高、带温度补偿芯片贵一点、I2C时序稍复杂对走时精度有要求
DOLED(SSD1306)内部RTC/外部模块体积小、显示酷炫I2C地址引脚调整麻烦想拿高分的进阶版

个人建议:课设首选B方案。理由就一条:DS1302的驱动代码网上遍地都是,出问题的概率最小;LCD12864的汉字显示效果,答辩演示时明显比1602有说服力。如果你想在答辩时多聊几句,可以把DS3231作为“后续优化方向”提一下,这部分我在文章后面会展开讲。

2. 硬件核心电路与原理图拆解

2.1 系统硬件构成概览

整套硬件电路可以分成五个模块:最小系统(单片机+晶振+复位电路)、时钟电路(DS1302 + 32.768kHz晶振 + 后备电池)、显示模块(LCD12864)、输入模块(独立按键)、提示模块(蜂鸣器,可选)。

这五个模块里,最容易出错的地方反而是看起来最简单的“最小系统”。很多人画原理图时把单片机的复位电路和晶振电路照抄网上的图,结果晶振不起振、复位不可靠,整个系统跑不起来,排查半天才发现是引脚画错了。下面我把每一块的接线和参数选择逐一说明。

2.2 单片机最小系统:以STC89C52为例

STC89C52是这项目里最常用的主控,它的引脚分布和功能务必要清楚。这里做个梳理:

引脚编号名称在本项目中的功能
9RST复位引脚,接10μF电容到VCC、10kΩ电阻到GND
18、19XTAL1、XTAL2接12MHz晶振,各接一个22~30pF负载电容到GND
20GND接地
31EA/VPP必须接VCC(高电平),否则单片机从外部ROM取指导致程序跑飞
40VCC接5V电源
P0口(39~32)P0.0~P0.7接LCD12864数据口D0~D7,必须外接10kΩ上拉电阻排
P2口(21~28)P2.0~P2.7接LCD12864控制线RS、RW、E,剩余接按键或蜂鸣器
P3口(10~17)P3.0~P3.7接DS1302时钟线(SCLK、IO、CE)及按键

这里有个经典大坑:P0口是开漏输出,内部没有上拉电阻,直接接LCD12864的数据线,会导致高电平驱动能力不足,屏幕显示乱码或者干脆白屏。解决方法是外接一个10kΩ的排阻(上拉排阻)接到VCC。这个细节在原理图里不体现出来,很多同学画完板才发现,在这儿卡两三天的人我见太多了。

复位电路用经典的“上电自动复位”即可:RST引脚接一个10μF电解电容到VCC,同时接一个10kΩ电阻到GND。上电瞬间电容充电,RST引脚短暂保持高电平,单片机复位;稳定后电容相当于开路,RST被电阻拉低,单片机开始运行。如果你用了内部复位电路增强型的STC芯片,也可以省掉外部复位电路,但做一个不亏,调试时还能手动按键复位。

2.3 LCD12864显示模块接线详解

LCD12864的接口定义比较统一,市面上大部分模组是20引脚或者16引脚。常用引脚功能如下:

引脚符号功能说明
1VSS电源地
2VDD电源正极,接5V
3VO液晶对比度调节,接10kΩ电位器到VCC或GND
4RS数据/命令选择端,接单片机P2.5
5RW读/写选择端,接单片机P2.6
6E使能信号,接单片机P2.7
7~14D0~D7并行数据总线,接P0口(配合上拉排阻)
15PSB并/串行接口选择,接VCC选并行模式
17/18背光正/负极背光LED供电,串接一个限流电阻

特别注意PSB引脚,它控制12864工作在并行还是串行模式。默认悬空时部分模组是串行模式,如果代码写的并行驱动却显示不出来,先检查这个引脚是不是接到了高电平。另外VO引脚如果不接电位器,屏幕会黑成一团或者淡得看不见字,这是新手最容易忽略的“显示效果异常”原因。

串行模式可以省掉D0~D7数据线,只需几根线就能驱动,但缺点是刷屏慢、写汉字的时候要一位一位移出去。在这个万年历项目里,数据量不大、刷新频率不高,用串行模式其实也可行,可以省下P0口做其他事。我这里按并行模式讲,因为并行驱动时序直观、适合理解底层原理。

2.4 DS1302时钟电路与后备电池设计

DS1302是Dallas公司(现在被Maxim收购)出的串行实时时钟芯片,采用三线接口(SCLK、I/O、CE),通信方式类似SPI但不完全等同。它最大的特点是带有涓流充电电路,可以给后备电池充电,保证系统断电后时钟依然运行。

电路接法相当简单:SCLK接P3.6,I/O接P3.7,CE(也可以叫RST)接P3.5。晶振用32.768kHz的,必须焊接在DS1302的X1和X2引脚之间,两边各接一个6pF负载电容。这里有个坑:32.768kHz晶振的频率容差直接决定走时精度,网上买的晶振里劣质品很多,出现过一天慢几十秒的。买的时候注意选±20ppm精度以内的,实测更省心。

后备电池用3V的CR2032纽扣电池,通过一个1N4148二极管隔离后接到DS1302的VCC2引脚。VCC1接主电源5V。DS1302内部会优先选择电压较高的一侧供电,掉电后自动切换到电池供电。二极管的作用是防止电池给系统其他部分反向供电,这个细节很多简化原理图没有画,实际产品上必须有。

2.5 按键电路与扩展设计

按键一般用独立按键,接P1口或者P2口剩余引脚,每路按键一端接IO,另一端接GND,IO内部上拉或者外部加上拉电阻。按下时IO读到低电平,松开读到高电平。万年历一般需要三个键:菜单/设置键、加键、减键。也有用四个键的,多一个“移位”键,调整时间时可以快速切换光标位置,操作体验好很多。

关于按键消抖,软件上做10ms~20ms延时消抖即可,硬件上如果能并联一个0.1μF电容更好,能滤掉大部分机械抖动带来的毛刺。这里建议别省软件消抖,很多程序“按键偶尔没反应”就是没做消抖造成的。

蜂鸣器属于可选模块,用NPN三极管(比如S8050)驱动,IO口通过1kΩ电阻接三极管基极,集电极接蜂鸣器负极,蜂鸣器正极接VCC,发射极接地。这样IO口输出高电平就能让蜂鸣器响,用于按键提示音或者整点报时。

3. 软件核心模块与代码实现要点

3.1 程序整体架构与模块划分

软件部分我不会把完整代码贴出来(那字数太长,而且网上资源很多),这里重点讲清楚“每个模块是干什么的、为什么这么写、关键地方怎么处理”。整体架构如下:

  • 主函数:初始化各模块,进入主循环,轮询按键,刷新显示
  • LCD12864驱动:命令写入、数据写入、清屏、光标定位、显示字符串
  • DS1302驱动:读时间、写时间、BCD码与十进制互转
  • 按键处理:消抖、识别单击/长按、菜单状态切换
  • 显示界面组装:把时间数据拼成字符串并定位到屏幕特定位置

这种“分模块”思路值得你刻意练习。课设答辩时老师最喜欢问“你程序整体结构是怎样的”,如果你能清晰说出这几个模块和它们之间的调用关系,分数比帖一堆代码高得多。

主程序流程图我用文字描述一下:上电后先延时等待系统稳定,初始化LCD12864、DS1302、定时器和按键IO,然后进入while(1)死循环。循环里做三件事:第一,每隔一段时间(典型50ms,用定时器标志位判断)读取DS1302的时间,刷新显示缓冲区;第二,扫描按键,判断是否有按键按下,有则进入对应对应菜单处理;第三,刷新LCD12864的显示内容。整体是个“轮询+标志位”的裸机架构,没有用操作系统,51单片机跑这种任务绰绰有余。

3.2 LCD12864驱动:并行时序与汉字显示

LCD12864并行驱动本质上是“写命令”和“写数据”两个操作的组合。每次操作步骤:

  1. 设置RS:写命令时RS=0,写数据时RS=1
  2. 设置RW:RW=0表示写(本项目只写不读,RW恒为0)
  3. 设置E:先拉高E,然后P0口输出要写入的数据,再拉低E。数据在E的下降沿被锁存到液晶控制器内部。

关键时序要求是:拉高E后,数据要保持一小段时间再拉低E,这个时间在12864的数据手册里是纳秒级,51单片机一条指令要1微秒,天然满足,所以不需要额外延时。但保险起见,操作完E之后再加一个几条空指令的短延时,避免跨操作间互相干扰。

代码骨架长这样(C语言):

void LCD_WriteCmd(unsigned char cmd) { LCD_RS = 0; LCD_RW = 0; LCD_E = 1; P0 = cmd; LCD_E = 0; delay_us(5); } void LCD_WriteData(unsigned char dat) { LCD_RS = 1; LCD_RW = 0; LCD_E = 1; P0 = dat; LCD_E = 0; delay_us(5); }

显示汉字的方法是利用12864内置的GB2312字库。在ST7920控制器的12864上,要显示一个汉字,直接发送该汉字编码的两个字节即可。比如要显示“星”字,发送0xD0 0xC7。实际编程时,把字符串传给显示函数,函数内部逐个字符判断:

  • 如果字节小于0x80,是ASCII字符,占1个字节
  • 如果字节在0x80~0xFF之间,是汉字编码的高字节,需要连续取两个字节发送

屏幕坐标系也要说清楚。ST7920的显示RAM分成四个区域,每行16×16像素,即每行显示8个ASCII字符或4个汉字。12864共4行,所以最多同时显示4行×4列共16个汉字。万年历界面规划时要按这个限制来排版,比如:

  • 第一行:2024年1月15日 星期一
  • 第二行:农历癸卯年 腊月初五
  • 第三行:09:30:25
  • 第四行:可以显示温度(如果加了DS18B20),或者留白

这里有个坑:12864的第二行和第三行不是连续的物理地址。第一行地址是0x80,第二行是0x90,第三行是0x88,第四行是0x98。如果按“第一行写完自动换第二行”的思路来写,会发现显示位置完全错乱。定位用的代码是LCD_SetCursor(行, 列),内部根据行列换算对应的地址码。

3.3 DS1302时序:三线通信与BCD码处理

DS1302的数据格式是BCD码,比如寄存器里存0x59,代表的是59秒,不是十六进制数0x59 = 89。所以读写时间后,必须做BCD码和十进制之间的转换。转换公式非常简单:

// 十进制转BCD unsigned char dec_to_bcd(unsigned char dec) { return ((dec / 10) << 4) | (dec % 10); } // BCD转十进制 unsigned char bcd_to_dec(unsigned char bcd) { return (bcd >> 4) * 10 + (bcd & 0x0F); }

DS1302的读时序,我是这么记的:CE拉高开始通信,SCLK产生脉冲,每个时钟的上升沿写入或移出一位数据。写操作是“CE拉高 → 发送命令字节(LSB在前)→ 发送数据字节(LSB在前)→ CE拉低”;读操作是“CE拉高 → 发送命令字节 → 读取数据字节 → CE拉低”。

命令字节有讲究:读秒寄存器是0x81,写秒寄存器是0x80,读分钟是0x83,写分钟是0x82,依此类推。最高位固定为1,最低位是读/写标志。第6位是“秒停止标志”控制位,写0启动时钟,写1停止时钟,调试时如果发现时间不走,先检查是不是误写了这个位。

操作DS1302时最容易遇到的问题:初始化后读回来的时间全是0xFF或者乱码。这多半是时序太紧或者引脚定义错误。解决办法是在每次CE拉高前加一个微秒级延时,SCLK拉高后、读写数据前也加延时。DS1302本身是慢速芯片,时钟周期要求在微秒级,单片机主频越高越需要人为插入延时。

3.4 定时器与按键调度

万年历项目里定时器一般有两个用途:一是提供时基,用于刷新显示、扫描按键、产生蜂鸣器提示音;二是如果不用DS1302,直接靠定时器中断做时钟计时(这种方式掉电时间丢失,不推荐)。

推荐的做法是:定时器0工作在方式1(16位定时),系统主频12MHz,12分频下定时器每1微秒计数一次,要产生50ms中断,初值为65536-50000=15536,即0x3CB0。每次中断里递增一个变量,累计20次达到1秒。这个1秒标志如何用,可以自己发挥,比如驱动秒信号闪烁“:”号之类的简单动画。

按键扫描放在主循环里比放在中断里更稳妥。因为中断里做延时消抖会让其他中断响应变慢。主循环扫描的典型流程:检测IO电平 → 有键按下然后延时10ms再检测 → 确认仍为低电平,判定有效按键 → 等待释放 → 执行功能。这个流程属于“教科书级”的消抖逻辑,实操中已经足够。菜单位置机用“设置界面”“调整年”“调整月”“调整日”“调整时”“调整分”“调整秒”“退出”这几个状态来表示,用一个变量记录当前状态,按键处理就根据状态决定执行什么操作。

3.5 显示刷新策略:避免闪烁与卡顿

很多人写显示刷新,就是在主循环里不停调用LCD显示函数。这不科学,因为12864整体刷一屏要写几百个字节,而且每次写命令/数据间都留延时,刷一屏可能要几十毫秒。如果每次主循环都刷,感觉上屏幕会“自己和自己打架”,尤其按下按键时响应变得迟钝。

正确的做法是:维护一份“显示缓冲区”,在主循环里只改缓冲区数据,然后通过标志位在需要时刷新到LCD。比如每秒到整秒时,只更新时间字符串,再调用一次对应行的显示函数。日期没变的时候不刷新日期行,只在按按键调整时才刷新。这样把“数据更新”和“屏幕刷新”解耦,代码清晰,屏幕也不闪烁。

如果你希望秒级数字变化流畅,可以只对“秒”所在的位置高亮反色或不反色,只重写这一区域,其他位置不动。ST7920支持局部地址设置,可以实现这种局部刷新。

4. 常见问题与调试技巧实录

4.1 LCD12864白屏或显示乱码

这是最常见的问题,几乎每个人都会遇到。排查顺序我建议按下面这个表来:

现象可能原因解决办法
白屏、完全无显示PSB脚悬空或接GND,工作在串行模式将PSB接VCC(并行模式)
屏幕有黑块/亮点VO对比度没调好接10kΩ电位器,调节对比度
显示乱码DB0~DB7接线不对或P0口缺少上拉核对接线,P0口加上拉排阻
显示错位行列地址定位错误检查SetCursor的地址码换算
显示一闪一闪刷新频率过高减少主循环刷新次数,改为标志位触发刷新

从实践来看,九成白屏都是PSB脚的问题,而不是程序问题。所以第一件事不是改代码,是用万用表量一下PSB引脚电压是不是高电平。我当年第一次用12864,白屏折腾了两天,最后发现是买的那块模组PSB引脚没焊接,补焊后立刻就好。

4.2 时间不走或者走时不准

时间不走,先检查DS1302的秒寄存器读回来是不是一直在变。如果读到的值一直不变,大概率是:

  • 晶振没起振(32.768kHz晶振虚焊或者质量差)
  • CE/SCLK/IO三根线接错
  • 命令字节的“秒停止位”被置1

走时不准(一天慢/快十几秒)则分两种情况:晶振频率偏差,或者焊点寄生电容过大。替换高精度晶振,或者把晶振两脚的负载电容减小一些(6pF换成3pF)会有改善。如果要求“对时精度很高”,可以直接换DS3231,它内部带温度补偿晶振(TCXO),年误差能控制在几分钟内。

这里补充一个实操技巧:DS1302的X1和X2引脚之间的走线尽量短,晶振附近不要铺铜,否则寄生电容会拉偏频率。用洞洞板搭项目时,晶振引脚尽量直接焊接在芯片旁边,不要拉长线。

4.3 按键响应不灵敏或“跳菜单”

按键问题多半出在消抖不彻底。如果按下一次按键,菜单跳了两个状态,说明主循环扫描频率太高,按键释放过程中的抖动被检测成了第二次有效按下。解决办法是:检测到按键按下后,一直等到按键释放再执行一次功能;或者加长消抖延时到15ms~20ms。

还有一种隐蔽的坑:按键IO口如果设置为“准双向口”模式(51默认的弱上拉模式),按下按键接地是没问题的;但如果代码里把按键口误配置成了推挽输出模式,按键按下去相当于IO口直接对地短路,短时间没事,时间久了可能烧引脚。所以写按键代码时别乱动P1口的模式配置,保持默认准双向即可。

4.4 程序烧录后反复复位或系统不稳定

这个很多时候是电源问题。STC89C52的功耗不高,但如果你的USB转TTL模块供电能力弱,或者供电线太长,系统工作电流一波动,单片机就复位。解决方法是USB供电线上并联一个100μF~470μF电解电容。另一个原因是复位电容太大,复位时间过长,如果用的是10μF电容还出现上电/掉电反复复位,可以换成4.7μF。

烧录时有个小坑:STC芯片用STC-ISP软件下载时,需要“冷启动”操作,断电再上电才能进入下载模式。如果你每次点下载都没反应,八成是没断电重新上电,或者下载器RX/TX和单片机TX/RX交叉接反。

4.5 调试工具与调试方法建议

有条件的话,强烈建议买一个逻辑分析仪,二三十块钱的8通道USB逻辑分析仪就够用。抓DS1302的时序时,逻辑分析仪能直接显示SCLK的脉冲和I/O线上的数据,一眼就能看出命令字节对不对、电平翻转是否正常。没有逻辑分析仪,至少用示波器量一下晶振引脚有没有振荡波形。

软件调试方面,常用的“printf重定向到串口”方法在51上也能用。把串口初始化好,重写putchar函数,串口助手就能看到单片机打印的调试信息。例如在关键节点打印“进入设置模式”“时间已更新”这类标记,比盯着12864屏幕猜状态高效得多。当然,程序板上打印信息只适合调试阶段,正式运行时可以把调试打印关闭或者删掉。

5. 扩展方向与个人经验总结

把基础版跑通之后,很多同学都会想加点功能让项目更有看点。我推荐几个难度适中、又符合课设/毕设“创新点”需求的扩展方向:

第一,加温度显示。接一个DS18B20数字温度传感器,单总线通信,只占用一个IO口,显示“当前温度 XX.X℃”,几乎不改动原有结构。第二,增加农历显示。农历换算可以查表法实现,把1900~2099年的农历数据压缩成一张大表,每年用3~4个字节存储,显示“X月X日 正月初五”没问题。这个功能比较讨巧,答辩时能讲出内容。第三,加入整点报时。配合蜂鸣器,每到整点响几声,用“几点了就响几声”的逻辑实现,属于纯软件功能。第四,连上WiFi或者蓝牙模块做时间同步。这个就需要换更强的单片机了,51做起来很吃力,但可以作为“后续展望”在答辩时提一嘴。

以我个人折腾这个项目的过程来说,最大的收获不是“把万年历做出来了”,而是学会了“按时序图写驱动”这一条通用技能。DS1302、LCD12864、DS18B20,这些芯片虽然不同,但本质上都是“按照时序图去操作引脚”“在合适的时间点给出合适的电平”。把这个思路刻进脑子里,以后再接触任何单片机外设,都不怵。

最后分享一个调试的小技巧:如果项目莫名其妙跑不起来,先用一个最简单的LED闪烁程序确认单片机是好的,再逐个模块往外加。我在做这个项目时,所有模块同时上电总是出问题,后来改成“先点亮LED→再驱动12864→再接DS1302→最后加按键”的顺序,每一步都验证通过再继续,整个流程顺畅多了。这个“分层验证、逐步集成”的思路,才是这个项目真正值得你掌握的东西。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询