☰
51单片机汇编实战:电子日历时钟从课设到可烧录工程
2026/11/2 3:22:00 网站建设 项目流程

简介:这份文档是华南理工大学2020年微机课程设计报告,面向学习微机原理、51单片机与汇编语言的学生及自学者,围绕电子日历时钟项目提供完整的设计思路与实现记录。报告从基本要求出发,涵盖北京时间显示、时间校准与汇编编程,并延伸至公历日期显示、日期校准、秒表、闹钟及PC联机校准等扩展功能;方案部分讨论了4位LED数码管静态与动态扫描驱动、矩阵键盘一键多用、定时中断基准时间、闰年平年判断及数码管余辉处理等关键问题,还记录了μvision编译环境、STC-10F08XE开发板与六种显示模式的具体操作逻辑。资源包共1个doc文件,约3.89MB,内容为课程设计报告书正文,结构完整、图文对应。已有1145人学习,适合作为微机课设选题参考、汇编编程范例与硬件接口设计的学习材料。

1. 一块 51 开发板上的电子日历时钟:从课程设计报告到能跑起来的汇编工程

拿到这份《电子日历时钟》课程设计报告时,我第一反应是:这玩意儿看着像交作业,实际上是一份相当完整的 51 单片机裸机工程。它用汇编语言在 μVision 里编译,跑在 STC-10F08XE 开发板上,靠 4 位 LED 数码管、矩阵键盘、INT0/INT1 两个外部中断和蜂鸣器,把北京时间显示、年月日显示、时间校准、日期校准、闹钟、秒表六件事塞进了一个不到几 KB 的程序里。适合谁?适合正在学微型计算机原理与接口技术、想搞懂定时器中断怎么变出“秒”、动态扫描怎么不糊屏、一键多用怎么设计的同学,也适合想拿一份能直接烧录的汇编模板去改自己课设的开发者。下面我按“它是什么 → 怎么跑 → 怎么改”拆一遍。

2. 硬件与软件方案拆解:4 位 LED、矩阵键盘和 50ms 定时中断怎么配合

2.1 硬件连接:74HC595 串行驱动数码管,INT0/INT1 管模式与校准

这套设计的硬件骨架很典型:4 位共什么极的 LED 数码管(报告没写死共阴共阳,从段码表用法看是段码驱动、位选独立),段码不直接挂 P0 口,而是通过 74HC595 串行输入、并行输出。为什么绕这一道?因为 51 的 IO 口本来就紧张,P0 要留低位做位选,P1.3 还要给蜂鸣器,直接用 595 能把“发一个字节”这件事封装成一个子程序,后面显示逻辑只关心字型码。

报告里给的寄存器定义已经把引脚说清楚了:

RCK BIT P0.5 ; 595 的锁存时钟 SRCK BIT P0.4 ; 595 的移位时钟 SER BIT P0.7 ; 595 的串行数据输入 RST BIT P0.6 ; 595 复位端,程序里 CLR RST 保持不复位 BUZZER BIT P1.3 ; 蜂鸣器

位选则是 P0 的低四位:ORL P0,#08H点亮最右一位,#04H第三位,#02H第二位,#01H第一位。注意这里 P0 高四位被反复用ANL A,#0F0H清掉,这是动态扫描里“先消影再写新内容”的硬性动作,后面避坑章节会专门讲。

按键这边,INT0(P3.2)负责切换六个显示模式,INT1(P3.3)负责进入/退出校准。矩阵键盘用 S11、S12、S15、S16 四个键做加减和位选,这就是报告里说的“一键多用”——同一个物理键在模式 0 下改年份的某一位,在模式 1 下改月份,在模式 2 下改小时,靠当前 MODE 变量分派,省掉了大量独立按键。

2.2 软件分层:主循环查模式,中断管计时和按键

程序结构不复杂,但分层是清楚的。主程序START先做闹钟比对,再按MODE变量跳转到六个显示子程序之一;定时器 0 每 50ms 中断一次,在中断里累加计数,凑够 20 次就是 1 秒;外部中断 0 和 1 只做“改状态、消抖、调子程序”,不干重活。

初始化部分值得逐行看,因为它决定了整个系统的时间基准:

MOV TMOD,#11H ; 定时器0和1都工作在方式1,16位定时 MOV TL0,#0B0H ; 50ms 初值低字节 MOV TH0,#03CH ; 50ms 初值高字节 MOV TL1,#0B0H MOV TH1,#03CH SETB TR0 ; 启动定时器0 SETB ET0 ; 允许定时器0中断 SETB EX0 ; 允许外部中断0 SETB IT0 ; 边沿触发 SETB EX1 SETB IT1 SETB EA ; 开总中断 SETB PT0 ; 定时器0高优先级 SETB PX0 ; 外部中断0次高

参数说明:TMOD=0x11表示两个定时器都用方式 1(16 位定时),12MHz 晶振下机器周期 1μs,50ms 需要 50000 个机器周期,初值 = 65536 − 50000 = 15536 = 0x3CB0,所以TH0=0x3C、TL0=0xB0。优先级设置成“定时器 0 > 外部中断 0 > 定时器 1/外部中断 1”,是为了保证秒计数不被按键扫描拖慢——这是血泪经验,优先级配反了秒表会明显走慢。

2.3 六个 MODE 的状态机:一个变量撑起全部交互

六个模式本质是一个循环状态机,MODE从 0 到 5,INT0 每按一次加一,到 6 归零:

INT0_NEXT: INC MODE ; 模式加一 ACALL BEEP_SHORT ; 按键提示音 MOV A,MODE CJNE A,#6,INT0R MOV MODE,#0H ; 溢出归零 INT0R: POP ACC RETI

这段在PRO_INT0里,前面有MOV R7,#10 / ACALL DELAY / JB P3.2,INT0R做软件消抖。逻辑说明:先延时约几十微秒再读引脚,如果还是低电平才认按键;TD变量在这里还承担“校准态”标记,TD=1时 INT0 不切模式而是把TD置 2,避免校准过程中误切模式。这种用一个小变量同时表达“当前模式”和“是否在校准”的做法,在资源紧张的 51 上很常见,但读代码时要盯紧TD的取值分支,否则容易看晕。

2.4 动态扫描显示:先消影,再发段码,再点位选

显示是这套代码里最容易翻车的部分。SEG_DISPLAY依次处理个、十、百、千四位,每一位的流程都是:清 P0 低四位(消影)→ 取字型码 →SEND_BYTE串行发给 595 → 置位选 → 延时保持 → 下一位。以个位为例:

DISPLAY1: MOV A,P0 ANL A,#0F0H ; 保留高四位,低四位清0,消影 MOV P0,A MOV A,GEWEI ACALL SEND_BYTE ; 段码串行送入595 ORL P0,#08H ; P0.3置1,点亮最右数码管 MOV R7,#10H LCALL DELAY ; 保持时间,保证亮度

SEND_BYTE用移位的方式把 8 位数据一位一位打到SER,每打一位给一个SRCK上升沿,8 位发完再给RCK一个脉冲锁存输出。参数上R7=#9是因为循环里先DJNZ再判断,实际发 8 位数据加一次收尾。延时R7=#10H是经验值,太短亮度不够,太长四位轮流点亮会看到闪烁;报告里也提到驱动信号维持时间要大于起辉时间、间歇要小于余辉时间,这段延时就是在这两个约束之间找平衡。

3. 从 μVision 到开发板:编译、烧录和第一次上电的完整步骤

3.1 建工程与编译:汇编文件怎么进 μVision

拿到源码后,第一步不是急着烧,而是在 μVision 里把工程搭起来。新建 Project,器件选 STC 系列里与 STC-10F08XE 对应的 8051 内核型号(没有完全一致的选通用 8051 也能编译,只是烧录工具要单独用 STC-ISP)。把报告里的.asm文件加入 Source Group,Options 里勾上“Create HEX File”,晶振频率填 12MHz——这个值影响不了汇编编译结果,但影响你对定时初值的判断,填错后面调时间会怀疑人生。

编译时如果报NUM_TABLE未定义,说明字型码表没贴全。报告正文只给了逻辑,字型码表要自己补,常见做法是按共阴/共阳写一张 0~9 加小数点的表:

NUM_TABLE: DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH,40H ; 0~9 及小数点(0x40),共阴接法,按实际硬件调整

注意最后那个40H是小数点,代码里MOV A,#10 / MOVC A,@A+DPTR取的就是它,用来在模式 2、3、4 里点小数点做分隔和状态提示。如果你的板子是共阳,整张表要取反,否则上电全是乱码——这是新手第一个坑。

3.2 烧录与上电自检:先看年份,再按 INT1

HEX 生成后用 STC-ISP 选串口、选型号、下载,冷启动上电。正常现象是数码管显示2020(模式 0 默认年份),按 INT0 依次看到0826、08.30、00.00、16.31、0.00.0。如果上电全黑,先查 595 的RST有没有被拉低——代码里CLR RST是故意的,595 复位端低有效,保持低是为了不让它复位,但有些板子这个脚接法不同,需要对照原理图确认。

自检顺序建议按报告的结果分析来:模式 0 按 INT1 进校准,按 S12 看年份是否从 2020 变 2120;模式 1 按 S12、S16 看月日增减;模式 2 看时分;模式 4 设一个一分钟后的闹钟,等蜂鸣器响。每一步都对上了,说明中断、扫描、按键三条链路都通了。

3.3 时间校准的按键映射:一张表看懂 S11/S12/S15/S16

校准逻辑集中在KEYSCAN1(报告正文没贴全这个子程序,但映射关系写得很清楚),按模式分派:

模式S11S12S15S16
0 年千位十位百位个位
1 月日月+1月−1日+1日−1
2 时分时+1时−1分+1分−1
3 秒毫秒秒十位秒个位——
4 闹钟时+1时−1分+1分−1
5 秒表清零开始暂停—

这张表就是二次开发时最该先吃透的东西:想加一个“星期”显示,就在模式 0 旁边插一个模式,再在这张表里补一列;想改成长按连加,就在KEYSCAN1里加计数判断。参数上要注意模式 0 的年份是拆成YEAR_H和YEAR_L两个字节存的,改千位和百位动的是YEAR_H,改十位和个位动的是YEAR_L,别一股脑往一个字节里塞。

4. 核心子程序逐段讲:定时中断、闹钟比对和秒表计数

4.1 定时器 0 中断:50ms 怎么累加成秒、分、时、日、月、年

报告正文没有完整贴出EINT0中断服务程序,但主程序里的初值和R6计数寄存器已经把思路交代了:每 50ms 进一次中断,R6加一,满 20 次清零并给秒加一。合格开发在这个场景下一般会这么写:

EINT0: PUSH ACC PUSH PSW MOV TL0,#0B0H ; 重装初值 MOV TH0,#03CH INC R6 CJNE R6,#20,EINT0_EXIT ; 20×50ms = 1s MOV R6,#0 INC SECOND MOV A,SECOND CJNE A,#60,EINT0_EXIT MOV SECOND,#0 INC MINUTE MOV A,MINUTE CJNE A,#60,EINT0_EXIT MOV MINUTE,#0 INC HOUR MOV A,HOUR CJNE A,#24,EINT0_EXIT MOV HOUR,#0 INC DAY ; 此处接月大月小、闰年判断 EINT0_EXIT: POP PSW POP ACC RETI

逻辑说明:方式 1 不是自动重装,所以每次进中断必须手动重装TL0/TH0,漏了这步时间会越走越慢。秒→分→时→日逐级进位,每一级都用CJNE判上限。日进到月这一层,就要处理报告里强调的“月大月小”和“平年闰年”:4、6、9、11 月 30 天,其余 31 天,2 月按(年%4==0 && 年%100!=0) || 年%400==0判 28 或 29 天。51 汇编里做取模不方便,常见做法是用减法循环或查表,我一般会写一个IS_LEAP子程序,把年份低字节拿去做 4 的整除判断,够用且省代码。

4.2 闹钟比对:主循环里每秒扫一次,相等就响三短一长

闹钟没有单独的中断,而是放在主循环START开头,每次循环都比一次当前时和闹钟时、当前分和闹钟分:

START: MOV DPTR,#NUM_TABLE MOV A,CLOCK_H CJNE A,HOUR,TIMER_EXIT ; 时不等,跳过 MOV A,CLOCK_M CJNE A,MINUTE,TIMER_EXIT ; 分不等,跳过 LCALL BEEP_CLOCK ; 相等,响铃 MOV R7,#40 LCALL DELAY TIMER_EXIT: MOV A,MODE ; 按模式跳显示

BEEP_CLOCK里用R4=#3循环三次短音,再补一次长音,靠CLR BUZZER / LCALL DELAY / SETB BUZZER控制节奏。参数上R7=#20H、#1EH、#30H、#4AH是不同长短的延时值,改这几个数就能调闹铃节奏。注意这个比对是“相等即响”,没有做“只响一次”的锁存,所以闹钟那一分钟内会反复触发,实际用起来会一直响到分跳变——想改成响一次,加一个ALARM_FLAG标志位,响过置 1,分变了清 0。

4.3 秒表:定时器 1 独立计时,显示时做除法拆位

秒表用定时器 1 单独计时,WATCH_MIN/WATCH_SEC/WATCH_MSEC三个字节存分、秒、毫秒。显示函数SHOW_WATCH里有一段除法拆位很典型:

SHOW_WATCH: MOV A,TD CJNE A,#0,SHOW_WATCH_NEXT CLR TR1 ; TD=0 时停表 SHOW_WATCH_NEXT: MOV A,WATCH_SEC MOV B,#10 DIV AB ; A=十位,B=个位 ADD A,WATCH_MIN MOV TEMP2,A MOV TEMP1,B MOV A,#10 MUL AB ADD A,WATCH_MSEC MOV TEMP1,A LCALL SEG_DISPLAY

逻辑说明:51 的DIV AB把 A 除以 B,商在 A、余数在 B,正好拿来拆十位和个位。WATCH_MIN这里被当成“分的十位”直接用,是因为秒表分钟数不会超过 9,省了一次除法。TD在这里又出现了,TD=0表示秒表停止,TD非 0 表示运行——同一个变量在按键校准和秒表里承担不同语义,读代码时一定要结合上下文,这也是这份代码可读性上最容易被吐槽的地方。

5. 避坑与排查:动态扫描、中断优先级和闰年判断的五个真实坑

5.1 数码管显示发虚、重影

现象:四位数字边缘有残影,快速变化时尤其明显。原因:动态扫描切换位选前没有把上一位的段码清掉,LED 余辉叠加。解决:每次写新段码前先ANL A,#0F0H / MOV P0,A把低四位清零,代码里DISPLAY1到LED1每一段开头都有这个动作,自己改显示逻辑时千万别省。

5.2 秒表越走越慢或时间不准

现象:跑一晚上慢好几分钟。原因:定时器 0 方式 1 不自动重装,中断里漏了重装TL0/TH0,或者中断服务程序太长导致丢中断。解决:进中断第一件事重装初值;把显示、按键扫描这类耗时操作挪出中断,中断里只做计数和标志位。

5.3 按键一按跳好几个模式

现象:按一次 INT0 模式跳两格。原因:机械按键抖动,边沿触发下多次进入中断。解决:中断入口先LCALL DELAY延时再读引脚,确认仍为低才处理,代码里MOV R7,#10 / LCALL DELAY / JB P3.2,INT0R就是干这个的;消抖延时别太长,否则按键响应变迟钝。

5.4 闰年 2 月 29 日不出现或 2 月 30 日出现

现象:设到 2020 年 2 月,28 日之后直接跳 3 月 1 日。原因:闰年判断只写了“能被 4 整除”,漏了“整百年要能被 400 整除”。解决:按(年%4==0 && 年%100!=0) || 年%400==0写全,51 上可以用年份低字节先判 4,再单独判 00 结尾的年份。

5.5 烧录后蜂鸣器一直响或上电就响

现象:一上电蜂鸣器长鸣。原因:BUZZER初值没设成高电平,或者闹钟默认时间恰好等于当前时间。解决:初始化里SETB BUZZER先关蜂鸣器;闹钟默认值设一个不会立即命中的时间,比如报告里的16:31,上电当前时间是08:30,不会误触发。

6. 二次开发改造思路:加星期、改长按连加和 PC 联机校准的落点

想把这份课设改成自己的东西,最省力的路径是沿着现有状态机加分支,而不是推倒重来。第一个改造点是加“星期”显示:在MODE里插一个模式 6,显示函数照抄SHOW_MONTH_DAY的结构,星期值用一个字节存,在日进位时同步加一、模 7 归零;按键映射表里给这个新模式留空或只做只读显示,改动量不到三十行。第二个改造点是长按连加:KEYSCAN1里现在是“检测到键就加一”,改成“检测到键后启动一个计数,每 200ms 再加一”,用一个KEY_HOLD_CNT变量在定时中断里递减,松手清零,这样调年份不用按到手酸。

第三个改造点是报告里提到但没展开的 PC 联机校准。51 上做这个,常见做法是用 UART 串口:初始化SCON=0x50、TMOD里给定时器 1 配波特率,主循环里轮询RI,收到一帧“年月日时分秒”就写进对应寄存器。协议不用复杂,六个字节加一个校验和就够,PC 端用串口助手手动发也行。这里有个我踩过的坑:串口中断和定时器 0 中断同时开的时候,如果串口中断里做太多解析,秒计数会抖,所以串口只负责收字节进缓冲区,解析放主循环。

验证方法上,我习惯每改一个功能就用“对照法”:改之前先记下当前显示值,改完烧录,按同样的按键序列走一遍,看变化是否只在预期的那一位。比如加星期后,模式 0 到 5 的显示必须和改之前一模一样,只有新模式多出来,这才说明没碰坏原有逻辑。

最后说个教训:这份代码里TD一个变量在按键校准和秒表里承担两种语义,我第一次读的时候在SHOW_WATCH里看到CJNE A,#0愣了半天,以为是校准标志。从那以后我每次接手这种“一个变量多处复用”的汇编工程,都强制先把每个 RAM 地址的读写点全部列出来再动手改,不然改一处崩三处。希望帮到你。

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

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

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

立即咨询