简介:本资源是一份面向汇编语言初学者与《微机接口与汇编语言》课程学习者的万年历显示程序实践项目,解决“用底层指令实现日历逻辑与屏幕输出”这一典型教学难点。程序完整实现键盘输入年月日、蔡勒公式计算星期几、闰年判断、光标定位、六日高亮(红色字体)、月首对齐及月末截断等核心功能,配套文档详述需求分析、流程图、子程序设计(二进制转十进制、闰年判定、星期推算)及源码注释,便于理解汇编级时间处理逻辑。压缩包为1个581KB的DOC文档,涵盖系统整体流程图、各模块详细设计说明与完整汇编源代码段,结构清晰,适合作为课程设计参考或实验报告范本。目前已有836人学习下载,是掌握INT 21H输入调用、INT 10H显存控制、数学公式嵌入汇编及错误处理机制的优质实操素材。
1. 万年历汇编程序:不是炫技,是把蔡勒公式、BIOS中断和光标定位焊死在80×25文本屏上的硬核落地
你有没有试过,在纯DOS环境、没有C库、没有printf、连字符串格式化都要手撕的条件下,让一个80×25的彩色文本屏,精准吐出2025年3月的日历——第一行是"SUN MON TUE WED THU FRI SAT",1号落在星期六,29号落在星期六,且所有周六日用红色高亮,输入的20250317当天日期再用闪烁蓝底白字标出来?这不是课程设计作业的幻觉,而是这份万年历汇编程序真实跑通的现场。它不依赖任何高级语言运行时,全程用INT 21H(DOS功能调用)收键盘、用INT 10H(BIOS视频服务)控光标/设颜色/清屏,用蔡勒公式硬算星期几,用查表+条件跳转处理闰年与大小月边界。它解决的不是“能不能显示”,而是“在寄存器资源紧张、段地址易错、堆栈深度有限、无调试符号”的真实汇编约束下,如何让日历不偏移、不越界、不错色、不崩溃。适合正在啃《微机接口与汇编语言》实验课、被INT 10H参数折磨得怀疑人生的本科生;也适合想回溯底层逻辑、验证自己是否真懂“光标定位=行×80+列”底层映射的老手——因为这里每一行MOV DH, ROW、每一条ADD DL, 5,都对应着显存里真实的字节偏移。别被“万年历”三个字唬住,它本质是一套在16位实模式下,对时间、屏幕、输入三者做原子级协同的微型操作系统雏形。
2. 从键盘到寄存器:INT 21H 0AH输入模块的缓冲区陷阱与数据解包实战
2.1 输入协议必须吃透:为什么STR1缓冲区要定义成DB 9, ?, 9 DUP(?)
程序中定义的输入缓冲区STR1并非随意而为,而是严格遵循INT 21H功能0AH的输入协议。该功能要求缓冲区首字节预存“最大可接收字符数”(含回车),第二字节由系统写入“实际接收字符数”(不含回车),后续字节才存放用户键入的ASCII码。因此:
STR1 DB 9 ; 首字节:最多接收9个字符(含回车) DB ? ; 第二字节:系统写入实际字符数(如输入"20090101"为8) DB 9 DUP(?) ; 后9字节:存放ASCII字符,足够覆盖8位年月日+回车提示:若此处首字节填错(如填8),当用户输入8位日期+回车(共9字符)时,系统会截断,导致最后一位数字丢失。这是新手最常翻车的第一步。
2.2 字符串转数值:手撕BCD→二进制的四轮乘加循环
输入的"20090101"是ASCII字符串,需转换为YEAR=2009、MONTH=1、DAY=1三个数值。程序采用经典左移累加法,以年份为例:
LEA DI, STR1+2 ; DI指向"20090101"首字符'2' MOV CX, 4 ; 年份占4位 MOV AX, 0 ; 初始化结果为0 P1: MOV BL, [DI] ; 取当前字符(如'2'→0x32) SUB BL, 30H ; ASCII转数字(0x32-0x30=2) MOV BH, 0 ; 清BH,确保BL为纯数字 MOV P, 10 ; P为乘数10(定义在数据段) MUL P ; AX = AX * 10(如0*10=0 → 2*10=20 → 20*10=200...) ADD AX, BX ; AX = AX + 当前数字(20+0=20 → 200+0=200 → 2000+9=2009) INC DI ; 指向下一字符 DEC CX ; 计数减1 JNZ P1 ; 未处理完4位,继续 MOV YEAR, AX ; 存入YEAR变量逻辑说明:每次循环先将已有结果×10(腾出个位),再加新数字。参数P必须为10,不可省略为立即数(因MUL指令只支持寄存器或内存操作数)。此循环对月份、日期同理,但位数不同(月/日各2位),需调整CX初值与DI偏移量(STR1+6取月,STR1+8取日)。
2.3 输入校验:大小月天数动态绑定与闰年判定的时序关键
输入校验不是简单比大小,而是构建“年→闰年标志→2月天数→当月最大天数”的链式判断。核心在于闰年判定必须在取月、取日后立即执行,否则无法填充BUF1中2月的占位符?:
CALL PR ; 调用闰年子程序(PR中用YEAR计算,CF=1为闰年) JC L1 ; CF=1跳转(闰年) LEA DI, BUF1 MOV [DI+1], BYTE PTR 28 ; 平年:BUF1[1] = 28(BUF1索引0起始,1对应2月) JMP L2 L1: LEA DI, BUF1 MOV [DI+1], BYTE PTR 29 ; 闰年:BUF1[1] = 29 L2: ...后续取月、取日...参数说明:BUF1 DB 31,?,31,30,...中?是2月占位符,[DI+1]即其地址。若校验顺序颠倒(如先取日再判闰年),则CMP DAY, CL中的CL仍为?(未初始化值),导致校验失效。这是血泪经验:所有依赖年份的校验,必须在YEAR写入后、MONTH/DAY解析前完成。
2.4 错误处理闭环:REINPUT标签与ROW寄存器的协同重置
错误提示非简单跳转,而是构建完整UI重绘闭环。当JC ERROR触发时:
ERROR: DEC ROW ; 行号回退(因上一步已ADD ROW,2) MOV DH, ROW MOV DL, 1 CALL GBIAO ; 定位光标到错误提示行首 LEA DX, WRONG CALL NINE ; 显示错误信息 INC ROW ; 行号下移,为下次输入留空行 JMP REINPUT ; 跳回输入流程起点关键点:ROW是全局行计数器,控制整个UI垂直布局。DEC ROW确保错误信息覆盖在原输入提示行,INC ROW后REINPUT重新绘制输入框,形成“提示→输入→校验→失败→覆盖提示→再输入”的零残留体验。若忽略ROW管理,错误信息会堆叠在屏幕底部,UI彻底混乱。
3. 日历生成核心:蔡勒公式在16位寄存器中的手工实现与1/2月特判
3.1 蔡勒公式的寄存器映射:为什么必须拆解W = [C/4] - 2C + y + [y/4] + [13*(M+1)/5] + d - 1
公式中每个项对应一次寄存器运算,不能直接写成一行(16位寄存器无浮点,除法需IDIV)。以2009年1月1日为例,按蔡勒规则需转为2008年13月:
; 假设输入MONTH=1, YEAR=2009 → 转为C=20, y=08, M=13, d=1 MOV BL, 100 ; 用于分离世纪与年份 MOV AX, YEAR ; AX = 2009 DIV BL ; AL = 20 (C), AH = 09 (y) MOV C2, AL ; C2 = 20 MOV Y, AH ; Y = 09 ; 计算 [C/4] - 2*C MOV CL, 2 MOV BL, C2 SHR BL, CL ; BL = [C/4] = 20/4 = 5 SHL C2, 1 ; C2 = 2*C = 40 SUB BL, C2 ; BL = [C/4]-2*C = 5-40 = -35 ; 计算 y + [y/4] ADD BL, Y ; BL = -35 + 9 = -26 SHR Y, CL ; Y = [y/4] = 9/4 = 2 ADD BL, Y ; BL = -26 + 2 = -24 ; 计算 [13*(M+1)/5] (M已修正为13) INC MONTH ; M+1 = 14 XOR AH, AH MOV AX, MONTH ; AX = 14 MOV DX, 13 MUL DX ; AX = 14*13 = 182 MOV CX, 5 DIV CX ; AL = [182/5] = 36 ADD BL, AL ; BL = -24 + 36 = 12 ; 加d-1(d=1 → d-1=0),最终W=12 MOV N, BL ; N = W mod 7 → 后续取模逻辑说明:SHR BL, CL实现整除4,DIV CX实现整除5。N存储W值,但最终需MOD 7得星期索引(0=Sunday, 1=Monday...)。此处BL可能为负,故后续用IDIV(有符号除法)并取余数。
3.2 1月/2月特判:为什么必须分J1/J2两支路径处理年份借位
蔡勒公式要求1月、2月视为上一年的13、14月,但“上一年”涉及世纪借位(如2000年1月→1999年13月)。程序用JNC J2检测年份后两位是否为0:
CMP Y, 1 ; Y是年份后两位(如2000→Y=00) JNC J2 ; 若Y>=1(即非00),直接SUB Y,1 ADD Y, 100 ; Y=00时,加100变99(1999年→y=99) J2: SUB Y, 1 ; 统一减1现象:若忽略ADD Y,100,2000年1月计算时Y=00→00-1=FFH(255),导致[y/4]=63,W值爆炸。此坑只有用DEBUG单步跟踪Y寄存器才能发现。
3.3 光标定位算法:B = 5 * N 如何把星期索引转为屏幕列偏移
日历标题"WEEK DB 'SUN MON TUE...'"中,每个星期缩写占4字符("SUN "),但程序用BUF3查表输出数字,每个数字占3字符(" 1"、"10"),故列偏移按5*N设计(预留空格):
MOV AL, 5 MUL N ; AX = 5 * N(N=0→SUN列=0, N=1→MON列=5...) MOV B, AX ; B存列坐标 ... MOV DH, ROW ; 定位到日历行 MOV DL, BYTE PTR B ; 列坐标 CALL GBIAO ; 设置光标 LEA DX, BUF3[SI] ; 取" 1$"等字符串 CALL NINE ; 显示数字 ADD SI, 5 ; 下一数字地址(每个字符串5字节) ADD B, 5 ; 下一列坐标参数说明:BUF3是预定义的31个数字字符串数组,每个以0AH,0DH,'$'结尾,SI按5字节步进。ADD B,5确保数字横向对齐,避免"1"和"10"因宽度不同导致错位。
3.4 周末高亮机制:INT 10H 06H功能如何用BH寄存器切换单元属性
周六(SUN列=0)、周日(SAT列=30)高亮非全局设色,而是对特定矩形区域调用卷屏功能(06H):
; 设SUN列为红色(列0-3) MOV AH, 6 MOV AL, 0 MOV CH, ROW ; 矩形顶行 MOV CL, 3 ; 矩形左列(SUN起始列) MOV DH, ROW ; 矩形底行 MOV DL, 0 ; 矩形右列(SUN结束列) MOV BH, 10110100B ; 背景蓝(1011)+前景红(0100) → 红字蓝底 INT 10H ; 设SAT列为红色(列30-33) MOV CL, 30 MOV DL, 33 INT 10H注意:
BH寄存器高4位为背景色,低4位为前景色。10110100B中1011=蓝色背景,0100=红色前景。若误写10110001B(蓝底白字),则高亮失效。
4. 避坑指南:调试中踩过的五个真实寄存器陷阱与修复方案
4.1 现象:输入正确日期后,日历第一行数字全部错位,且周六日高亮区域偏移
原因:MOV CX,4误写为MOV CL,4,导致高位CH残留旧值(如CH=02H),使CX=0204H而非0004H,后续DEC CX循环次数错误。
解决:统一使用MOV CX,4,或在使用前XOR CX,CX清零。调试时用DEBUG观察CX寄存器变化,确认其值为预期4。
4.2 现象:1月、2月日历星期计算全错,其他月份正常
原因:蔡勒公式特判分支中,SUB Y,1前未处理Y=00情况,导致Y变为FFH(255),[y/4]计算溢出。
解决:增加CMP Y,1+JNC J2分支,Y=00时先ADD Y,100再SUB Y,1,使Y变为99。
4.3 现象:输入日期"20090101"后,屏幕显示"Date:20090101"但随后崩溃
原因:DATE:提示代码位置错误,被放在CMP DAY,CL校验之前。当输入"20091301"(13月)时,CMP MONTH,13跳转JNC ERROR,但DATE:已执行,导致错误提示与输入混杂。
解决:将DATE:显示代码整体后移至所有校验(CMP MONTH、CMP DAY)通过之后,确保只对合法输入显示原始字符串。
4.4 现象:日历数字显示为乱码(如" 1"显示成"?")
原因:BUF3字符串未以'$'结尾,或LEA DX,BUF3[SI]中SI越界(如SI=155超出BUF3长度)。
解决:检查BUF3定义末尾是否有'$';用DEBUG查看DX寄存器值,确认其指向有效内存地址;在CALL NINE前加CMP SI,155+JB SAFE保护。
4.5 现象:清屏后背景色为黑色,但日历区域背景色异常(如出现绿色条纹)
原因:INT 10H 06H清屏时BH=10110000B(蓝底白字),但后续周末高亮调用BH=10110100B(蓝底红字)后,未重置BH,导致后续CALL NINE(显示字符串)继承了错误的属性。
解决:每次调用INT 10H前显式设置BH,或在NINE子程序开头PUSH BH、结尾POP BH保存恢复。
5. 屏幕渲染优化:从基础显示到色彩层次与视觉动线的工程化打磨
5.1 色彩分层设计:背景、标题、周末、输入日期的四级色阶体系
程序未滥用颜色,而是构建清晰的视觉优先级:
- 背景层:
BH=10110000B(深蓝底+灰字)→ 全局基底,降低视觉疲劳 - 标题层:
BH=10110001B(深蓝底+白字)→A1/A2/A3等标题,亮度略高于背景 - 周末层:
BH=10110100B(深蓝底+红字)→SUN/SAT列,高对比度突出 - 焦点层:
BH=10110001B(深蓝底+白字)→ 输入日期"20090101",与标题同色但独立定位
提示:所有
BH值均以1011开头,确保背景色统一为深蓝(1011₂=11₁₀=蓝色),避免色系冲突。若需修改,仅调整低4位前景色。
5.2 视觉动线控制:ROW寄存器作为UI垂直坐标系的中枢调度
ROW是贯穿全程的垂直布局引擎,其值决定所有元素的Y轴位置:
| 模块 | ROW初始值 | 功能 | 关键指令 |
|---|---|---|---|
| 标题区 | 1 | 显示A1/A2/A3 | ADD ROW,2(每行标题占2行) |
| 输入框 | ROW+2 | 定位A4和输入区 | MOV DH,ROW+CALL GBIAO |
| 日历区 | ROW+1 | 日历标题WEEK | INC ROW后CALL GBIAO |
| 数字区 | ROW+1 | 第一日位置 | MOV DH,ROW+MOV DL,BYTE PTR B |
此设计使UI具备强可维护性:若需整体下移,只需修改ROW DB 1为ROW DB 3;若需标题区加空行,改ADD ROW,2为ADD ROW,3。所有元素自动跟随,无需逐个调整DH。
5.3 数字对齐工程:BUF3查表法如何规避sprintf类格式化开销
不用循环拼接数字(如1→'1'、10→'1','0'),而是预定义31个固定宽度字符串:
BUF3 DB ' 1',0AH,0DH,'$' ; 占3字节:" 1"(空格+数字+换行) DB ' 2',0AH,0DH,'$' ; 所有1-9统一" X"格式 DB '10',0AH,0DH,'$' ; 10-31统一"XX"格式 ...优势:
- 零计算开销:
LEA DX,BUF3[SI]直接取地址,无除法/取模 - 绝对对齐:每个字符串严格3字符宽,
ADD B,5确保水平间距恒定 - 内存换性能:31×5=155字节,远小于运行时格式化的代码体积
若改为动态生成,需额外DIV 10拆位、ADD AL,30H转ASCII、STOSB存字符串,代码膨胀且易出错。
5.4 错误提示的UX细节:为什么WRONG消息后要INC ROW
WRONG DB "The date you input is wrong!Please try again$"长度约40字符,超出行宽80的一半。若不INC ROW,下一次REINPUT绘制输入框时,MOV DH,ROW会定位到WRONG消息行内,导致输入框与错误信息重叠。INC ROW确保输入框始终在错误消息下方空行处,符合DOS交互直觉。
6. 实战验证与交付技巧:用DEBUG单步验证蔡勒公式与光标坐标的终极方法
6.1 蔡勒公式验证:在DEBUG中锁定W值计算全过程
不要依赖最终显示结果,直接在GETN子程序入口打T断点,单步跟踪关键寄存器:
-a 100 :100 MOV AX,2009 ; 设YEAR=2009 :103 MOV MONTH,1 ; 设MONTH=1(触发1月特判) :106 G 100 ; 运行到100h -d ds:0 100 ; 查看数据段,确认YEAR/MONTH已设 -t ; 单步执行,观察AX/Y/C2变化重点关注:
DIV BL后AL是否为20(C=20)、AH是否为09(y=09)SHR BL,CL后BL是否为5([C/4]=5)IDIV CL后AH是否为0(W mod 7余数)
若AH=6,则1号为Saturday(0=Sunday→6=Saturday),与万年历对照验证。
6.2 光标坐标验证:用DOS DEBUG的D F000:FF00读取当前光标位置
BIOS在F000:FF00存放当前光标位置(16位值,高字节行,低字节列):
-d f000:ff00 ff01 F000:FF00 01 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ; 此处0100H = 行1,列0 → 光标在第1行第0列在CALL GBIAO前后执行此命令,确认DH/DL值与F000:FF00读出值一致。若不一致,说明INT 10H 02H调用失败(如BH未置0)。
6.3 色彩属性验证:用DEBUG修改BH寄存器实时调试颜色
在INT 10H调用前,手动修改BH值观察效果:
-r bh BH 10110000 :10110100 ; 修改为蓝底红字 -t ; 执行INT 10H,观察周末是否变红此法比反复编译快10倍,是调试色彩问题的后悔药。
6.4 交付检查清单:一份可直接烧录的汇编程序必须满足的五项硬指标
交付前用此清单自检,避免因环境差异导致演示翻车:
- 段定义完整:
DATAS SEGMENT/STACKS SEGMENT/CODES SEGMENT三段齐全,END START结尾 - 字符串终结符:所有
DB "...$"中$不可遗漏,否则INT 21H 09H显示越界 - 缓冲区容量:
STR1 DB 9,?,9 DUP(?)首字节≥输入位数+1(8位日期+回车=9) - 寄存器保护:所有子程序(
PR/GETN/GBIAO)开头PUSH、结尾POP,避免主程序寄存器污染 - DOS版本兼容:使用
INT 21H而非INT 2FH,确保MS-DOS 3.3+均可运行
从那以后我每次写汇编输入模块,都强制走一遍STR1缓冲区协议三连问:首字节填对了吗?第二字节被系统写了没?DUP(?)够不够存回车?——因为一个?的缺失,足以让整个日历在演示现场变成乱码瀑布。希望帮到你。
本文还有配套的精品资源,点击获取