
简介面向51单片机学习者和电子设计初学者这套完整的基于51单片机的电子钟设计资源同时提供数码管和LCD1602两种显示方案。项目以DS1302时钟芯片为基础支持时分秒独立设定、秒复位以及日期时间切换复位后时间归零功能实用适合课程设计或毕业设计参考。压缩包共82个文件约841KB内含C语言源程序.c/.h、Keil工程文件.uvproj、Proteus仿真文件.DSN、原理图.SchDoc/.PDF与流程图.bmp还整理好了元件清单.xlsx和功能说明文档从硬件连接到软件逻辑均有清晰呈现便于复现和调试。目前已有536人学习资源包内还附带仿真运行所需的完整工程、预览图片及PDF原理图下载后可直接在Proteus中打开仿真也可以对比学习两套代码的差异。整套方案可运行、可修改能帮助初学者快速掌握51单片机定时器、按键处理、数码管/LCD显示等核心知识点边学边练。1. 51单片机电子钟数码管与LCD双输出的第一块硬骨头很多人在流水灯和蜂鸣器之后接的第一个真正带“时间”概念的项目就是电子钟。但你会发现一个现象网上搜“51单片机电子钟”能出来一堆压缩包点开之后要么只有数码管版本要么只给LCD版本想同时看两套显示逻辑就得自己拼而拼出来的代码往往一上电就走时不准Proteus 里跑得飞快烧到板子上又慢半拍。这个标题其实涵盖了完整的课设/毕设级项目链路原理图、物料清单、Proteus仿真、流程图、源代码缺一不可。它练的不是“让数码管闪数字”而是三件事用定时器构建稳定的时间基准、用动态扫描驱动数码管、用总线方式驱动LCD并且让两条显示链路在一个系统里共存。适合正在做课程设计、准备竞赛基础模块、或者想搞明白“为什么我的时钟走快了”的工程师。接下来我按自己做这类项目的顺序展开。2. 电子钟硬件设计51单片机引脚分配、原理图与物料清单2.1 数码管与LCD双显示怎么共存先想清楚一件事数码管和LCD都要显示时间但它们对IO的需求完全不同。六位共阴数码管需要8根段选线加6根位选线总共14个IOLCD1602用8位数据总线加RS、RW、E三根控制线总共11个IO。AT89C51总共才32个IO如果两套各自独立电平输出端口直接打架。我的习惯是把显示任务分到不同端口数码管段选占用P0口位选占用P2口LCD1602数据线占用P1口控制线占P3口的低三位。这样数码管扫描和LCD写入在程序上互不干扰还能避免动态扫描时的总线竞争。模块信号引脚说明数码管段选a~g, dpP0.0~P0.7经220R限流接共阴数码管段脚数码管位选bit0~bit5P2.0~P2.5经PNP三极管驱动公共端LCD1602数据DB0~DB7P1.0~P1.7八位总线方式LCD1602控制RS/RW/EP3.0/P3.1/P3.2RW接地时可不占用独立按键key1~key3P3.3/P3.4/P3.5带10k上拉电阻这样安排的好处是动态扫描的实时性要求高放在P0和P2操作指令短LCD速度慢放在P1不会被扫描中断打断。如果你用四位数码管位选引脚可以省出两位但六位才是数字钟的常见形态分秒各占两位。2.2 最小系统与数码管驱动电路51单片机最小系统本身很简单晶振加两个30pF电容作时钟源10uF电容加10k电阻作上电复位。但有两个地方容易被忽略。第一个是P0口内部没有上拉电阻作为段选输出驱动高电平时必须外接排阻。第二个是位选不能直接用P2口硬推六个数码管的公共端共阴数码管位选需要拉低公共端才能导通但一个IO口灌电流能力有限托不动六位数码管同时刷新时的峰值电流。成熟方案是位选接PNP三极管型号可选8550。P2口输出低电平时三极管导通VCC流向数码管公共端段选再决定哪几段发光。这个电路在Proteus里直接用开关器件也能仿真出来但如果你后期要打板三极管型号必须写进物料清单。段选每一路串一只220R电阻既限流又可以把亮度控制在一致范围。这里有一个仿真和实物的差异要提醒Proteus里的数码管模型对灌电流要求不高很多人不用三极管也能看到正常显示效果于是实物焊接时也省掉结果P0口冒烟或者P2口发烫显示亮度还贼暗。所以我建议在原理图阶段就按实物标准画全。2.3 LCD1602模块接线与对比度调节LCD1602采用8位总线方式接入数据口并到P1。三个控制脚里RW接低电平表示只写不读忙标志位RS由写命令还是写数据决定E脚是使能脉冲下降沿锁存数据。VL脚必须接一支10k电位器到GND用来调节对比度。很多人仿真时把这个脚直接接地显示出来的不是蓝块就是白块字符完全看不出来以为代码写错了。从原理图绘图习惯上说电源和地的节点标号要清楚晶振部分单独画一块数码管和LCD分区域布置。Proteus中原件放置建议按这个顺序单片机放中央左侧接晶振复位上方接LCD右侧接数码管下方接按键信号线尽量短。2.4 物料清单如果要照着清单备料实际焊接和仿真略有出入。表格里给出的是实物清单Proteus仿真时排阻和晶振不一定必须放但为了模拟真实负载建议保留。序号元件规格/型号数量说明1单片机AT89C51 或 AT89S521AT89S52可ISP下载2晶振12MHz1用于定时器时基计算3电容30pF2晶振负载电容4电解电容10uF/16V1复位电容5电阻10k2复位/上拉6排阻10k×81P0上拉7限流电阻220R8段选限流8三极管8550(PNP)6位选驱动9数码管六位共阴1可拆成两只三位10LCD模块1602带背光15V版本11电位器10k1LCD对比度12按键轻触式3功能/加/减3. Proteus 8仿真图搭建元件放置、连线与HEX固件加载3.1 元件选取与接线顺序Proteus 打开新工程后在元件选择器里输入关键字就能找到对应模型。我用的是 Proteus 8 Professional不同版本界面略有差异但查找逻辑一样。单片机选AT89C51在Microprocessor ICs类别下数码管搜7SEG-MPX6-CC这是六位共阴数码管注意别选成-CA共阳LCD1602 在Optoelectronics类下搜索LM016L这就是默认的字符液晶模型。连线时先把单片机周围的最小系统画完。晶振两端接30pF电容到GND复位脚接10k电阻到GND、10uF电容到VCCP0口接上拉排阻。这些细节决定后面仿真能不能稳定跑起来。LCD的对比度脚VL通过电位器接GNDP1.0~P1.7接数据总线P3.0、P3.1、P3.2分别接RS、RW、E。数码管段选接P0每组段选引脚间串一只220R电阻位选接P2口到三极管基极。如果仿真中想简化元件数量位选可以直接通过一个200R电阻接到P2口。Proteus里大量连续会显得原理图不可读。我的习惯是给总线打标签段选总线标成SEG_A~SEG_G位选总线标成DIG0~DIG5。用Property Assignment Tool给多个引脚批量添加上网络标号省时且不易漏线。3.2 仿真图纸上的关键检查点画完图后先别急着加载程序做三个静态检查。第一数码管公共端有没有正确接到位选侧第二P0口上拉排阻是否真实存在第三LCD的RW脚是否为低电平如果悬空在仿真里会导致写入无效。动态扫描的总线处理六位数码管的段选全部并接在同一组8根线上靠位选逐位导通来区分显示哪一位。这意味着段选电阻只有一组位选三极管也只有六个。检查总线时不要只看线连没连要确认哪一根线接到数码管的第几脚否则显示出来的数字会乱序跳。启动仿真后如果什么都不亮双击单片机看加载的固件路径是否有效再看晶振频率设置。Proteus中元件的Clock Frequency属性要改成12MHz和Keil工程里选的晶振保持一致。这个属性不设一致定时器时基会全部错乱。3.3 Keil生成HEX文件与加载方法Keil C51工程建立时选择Atmel AT89C51不要选成ARM内核。Options for Target 里切到 Output 选项卡勾选Create HEX File然后编译。生成的HEX文件路径默认在工程目录下的Objects文件夹里也可以自己在Output Name栏重新指定。回到Proteus双击原理图里的单片机元件在Program File一栏选择刚才生成的HEX文件。如果你后续改了代码重新编译需要在Proteus里重新加载一次HEX或者直接把单片机的Program File路径设到Keil的Objects目录每次改代码后重启仿真即可自动更新。仿真调试时一个实用技巧是把LCD显示内容作为寄存器状态的可视化手段。比如在主循环里周期性把秒变量写入LCD的某两个固定位置就能方便观察时间变化。Proteus的交互仿真速度接近真实时间肉眼可以直接看走秒不需要额外虚拟仪器。4. 软件时间基准与主流程图定时器中断到秒计数4.1 为什么选定时器中断而不是delay很多初版代码会用几个嵌套的for循环产生延时再调用延时函数去凑出1秒。这在裸机仿真下看似可行但当你加上按键扫描和动态显示后主循环每条指令的执行时间都在变化所谓1秒延时会被挤成1.2秒而且按键检测也会因为长延时变得迟钝。电子钟的时间基准必须独立于主循环也就是用定时器中断产生固定节拍。常见做法是使用定时器0工作于方式1也就是16位计数器模式选择12MHz晶振机器周期为1us最大溢出周期65.536ms。为了凑整设定时周期为50ms中断20次就是1秒。这样主循环可以随便执行显示刷新和按键处理时间累计完全由中断保证不受主程序分支影响。4.2 定时器初值计算与中断服务函数12MHz晶振下机器周期是1us。要定时50ms需要计数50000个机器周期定时器初值为 65536 - 50000 15536换算十六进制是0x3CB0。写成代码就是给TH0装0x3C、TL0装0xB0。但这里有个工程上的细节中断响应、保护现场和重装初值都需要时间所以实际初值通常会比理论值小一点具体偏多少用示波器校正下面代码先按理论值给出。// 定时器0初始化12MHz晶振50ms中断周期 void Timer0_Init(void) { TMOD 0xF0; // 只修改T0部分保留T1设置 TMOD | 0x01; // T0工作在方式116位定时器 TH0 0x3C; // 初值高8位65536-50000155360x3CB0 TL0 0xB0; // 初值低8位 ET0 1; // 使能T0中断 EA 1; // 开总中断 TR0 1; // 启动定时器0 } // 中断服务函数每50ms进入一次 void Timer0_ISR(void) interrupt 1 { TH0 0x3C; // 重装初值 TL0 0xB0; // 重装初值 tick_count; // 50ms累计 if (tick_count 20) // 20次溢出 1秒 { tick_count 0; second; // 秒加1主循环负责显示 if (second 60) { second 0; minute; if (minute 60) { minute 0; hour; if (hour 24) hour 0; } } } }这段代码里TMOD 0xF0和TMOD | 0x01分开写是为了不动定时器1的配置位。中断服务函数里重装初值放在计数的前面万一中断处理时间偏长下一轮定时也从重装那一刻开始算误差只在当前这一轮体现不会累积到后续周期。tick_count是全局变量中断里修改主循环里只读取不需要加volatile但如果你在中断外做判断还是建议声明为volatile unsigned char。4.3 主程序流程图与控制逻辑这个项目的流程图不需要画得很花哨核心是中断流程和主循环流程的配合。主流程顺序如下系统上电→关闭看门狗如果有→初始化定时器→清显示缓冲→进入主循环。主循环内部做三件事扫描按键、把时间变量拆分到显示缓冲、调用显示刷函数。整个循环没有长延时执行一次不超过几毫秒。中断流程在4.2的代码里已经体现50ms到→重装→tick加一→秒分时进位。两条流程通过second/minute/hour这三个全局变量交互。主循环需要确认一个原则时间变量只在中断里修改主循环只读这样不会出现半更新状态。如果你在按键调时里面直接改hour建议先关闭定时器中断修改完再开防止调时过程中秒中断进位覆盖。流程图可以用状态机来理解系统有两个状态正常运行和调时模式。运行状态时主循环持续刷新显示调时状态时按一下功能键切换调时对象时→分→秒加减键修改当前选中单位。状态切换通过一个enum型变量控制这是最清晰的可读写法。5. 数码管动态扫描与LCD1602驱动的源代码实现5.1 七段数码管显示0到9的段码映射七段数码管显示0~9的段码是每个做这个项目的人都要背的基本表。共阴数码管段a对应P0.0dp对应P0.7要点亮某段就往对应位置写1段码表定义如下// 共阴数码管段码索引0~9对应数字10为熄灭 unsigned char code seg_table[] { 0x3F, // 0: a,b,c,d,e,f 0x06, // 1: b,c 0x5B, // 2: a,b,d,e,g 0x4F, // 3: a,b,c,d,g 0x66, // 4: b,c,f,g 0x6D, // 5: a,c,d,f,g 0x7D, // 6: a,c,d,e,f,g 0x07, // 7: a,b,c 0x7F, // 8: 全部段 0x6F, // 9: a,b,c,d,f,g 0x00 // 熄灭 };如果你用的是共阳数码管段码表需要按位取反例如0变0xC01变0xF9。这个差异是初学者最容易犯的错误代码烧进去以后一位显示的数字带着消隐的尾巴或者干脆每位都在闪乱码基本就是共阴共阳搞反了。5.2 六位数码管动态扫描实现动态扫描的原理是利用视觉暂留在极短时间窗口内只点亮其中一位其余位全部熄灭轮完六位再从头开始。只要刷新频率高于人眼可分辨的闪烁频率看起来就像是六位同时在显示。我用的扫描周期是2ms一位六位一轮12ms刷新率约83Hz肉眼观察不到闪烁。// 每2ms切换一位显示需要放在主循环或定时器中断里周期性调用 void Display_Scan(void) { unsigned char i; for (i 0; i 6; i) { P2 0x00; // 先熄灭所有位防止拖影 P0 seg_table[disp_buf[i]]; // 送段码 P2 (1 i); // 选中第i位共阴位选低电平导通 delay_us(2000); // 保持2ms } }disp_buf是一个长度为6的数组分别存放秒个位、秒十位、分个位、分十位、时个位、时十位对应的数字索引。主循环把hour/minute/second拆成单个数字写入这个数组扫描函数只负责查表输出。注意P2 0x00这一行它是消除拖影的关键。如果不先把所有位关掉就直接送新的段码上一个位选的残余电流会把新段码显示在旧位上视觉上就是数字有残影。在Proteus里拖影不明显但实物上很扎眼。5.3 LCD1602初始化与时间显示LCD1602的初始化序列是固定的延时等待内部上电→写功能设置指令0x388位总线、两行→开显示0x0C→清屏0x01→光标右移0x06。每条指令之间要留足够的延时尤其是清屏指令需要等待约2ms。注意在Proteus的LM016L模型里延时稍长些更稳定代码里用延时函数实现即可。// LCD1602底层命令写入 void LCD_WriteCmd(unsigned char cmd) { P1 cmd; // 数据口送命令字 P3_0 0; // RS0命令模式 P3_1 0; // RW0写模式 P3_2 1; // E1拉高使能 delay_us(1); P3_2 0; // E0下降沿锁存 } // 初始化LCD必须在系统上电后最先调用 void LCD_Init(void) { delay_ms(15); // 等待LCD内部复位 LCD_WriteCmd(0x38); // 8位数据总线双行显示 LCD_WriteCmd(0x0C); // 开显示光标隐藏 LCD_WriteCmd(0x06); // 写入后光标右移 LCD_WriteCmd(0x01); // 清屏 }注意这段代码里的点P3_0依赖Keil C51的sbit定义实际使用需要先用sbit LCD_RS P3^0;声明。LCD写数据时只要把RS置1其他时序与写命令相同。清屏之后第一行显示日期或“TIME”第二行按“HH:MM:SS”的格式显示时间。一种实用排版是把小时、分钟、秒的变量分别转成ASCII码字符填入disp_str[0] hour/10 0;这种方法不需要库函数转换效率高代码也没有依赖。5.4 按键扫描与调时联动逻辑按键扫描放主循环去抖用延时消抖加电平确认。按键按下的瞬间会触发多次抖动直接读取会产生一次按键多次触发的问题。常见做法是检测到低电平后延时20ms再读一次如果还是低电平才认为是一次有效按键。// 按键检测按下返回1未按下返回0 unsigned char Key_Scan(void) { if (KEY1 0) // 检测到按下 { delay_ms(20); // 跳过抖动区间 if (KEY1 0) // 再次确认仍为低电平 { while(KEY1 0); // 等待松开 return 1; } } return 0; }调时逻辑通过功能按键循环切换正常→调小时→调分→正常。加减键在调时模式下修改数值并立即刷新显示。这个过程中有一点要特别留意如果调的是小时刚好到59分59秒下一秒就要触发进位。我的做法是在按键修改时间时关闭总中断EA 0修改完成后再打开保证修改操作不被中断里的进位干扰。6. Proteus联调与时间精度校正技巧6.1 用Proteus虚拟示波器验证秒周期Proteus自带的虚拟示波器可以直观观测时间基准是否正确。在原理图上放一个虚拟示波器从虚拟仪器面板选择找一个空闲的IO口比如P3.7在秒变量加一的那一行加上一个引脚翻转操作。程序运行后示波器上出现一个每两秒翻转一次电平的方波测量它的高电平或低电平宽度是不是精确的1秒。如果测出来是0.95秒说明初值算小了定时器溢出比预期早如果是1.05秒说明初值偏大。这个偏差主要来自中断响应和重装初值两条指令本身耗掉的时间。补偿方式是调整TH0/TL0的初值走快了就加大初值走慢了就减小初值每次调整1到2个机器周期对应的数值再用示波器复查。6.2 分钟级误差的实测校正秒脉冲测完还要做长时间稳定性验证。让仿真跑10分钟用LCD显示时间同时用系统真实时间去对比。假设仿真走了600秒实际时间过去了602秒说明钟偏慢0.33%。原因是12MHz晶振的频率误差很小真正的误差来源是定时器初值在中断重装时有丢失周期。把慢掉的秒数平均到每个50ms周期将这个差值换算成计数初值的调整量更新到代码里重编重新加载。这里有一个常见认知误区在Proteus里调出来的补偿值不一定完全适用于实物板子。因为Proteus模拟的是理想晶振但实物晶振的负载电容、温度漂移、焊接工艺都会影响实际走时。更合理的做法是把Proteus当作验证逻辑的手段最终精度靠实物调试时用频率计或秒表对表校准。6.3 扩展让同一套代码驱动不同输出介质前面把数码管和LCD绑定在固定端口但如果后续项目要切换输出介质可以定义两个独立模块显示数据统一从一个全局时间结构体里读数码管按动态扫描刷新LCD按位写入字符串。这样即使换了端口也只需要修改接口宏定义不用动业务逻辑。类似的这个电子钟框架可以直接扩展出闹钟、整点报时、倒计时功能闹钟只需要在秒中断里比较当前时间和闹钟设定值触发时翻转一个蜂鸣器引脚。Proteus仿真的意义在这里体现得最明显——你可以把功能模块一个个往上叠加而不用反复烧写实体芯片。遇到调时状态与非调时状态的显示冲突时回到5.4的按键扫描逻辑重新设计状态机比在中断里加一堆判断要可靠得多。本文还有配套的精品资源点击获取