首页 > 编程语言 >汇编语言(第4版)王爽 课程设计2 详细实现(三、界面优化篇)

汇编语言(第4版)王爽 课程设计2 详细实现(三、界面优化篇)

时间:2024-03-25 18:32:11浏览次数:22  
标签:课程设计 汇编语言 ah mov al cx push bx 王爽

目录

0. 前文概述

1. 子界面文字说明

1.1. 时钟显示

1.2. 时钟设置

2. 光标处理

2.1. 字符串输入光标

2.2. 新的问题

3. 完整代码

4. 总结


0. 前文概述

前面两篇文章中我们编写了一个可以自行启动计算机,不需要在现有操作系统环境中运行的程序。

第一部分我们实现了程序在虚拟模式DOS下的任务逻辑部分

课程设计2 详细实现(一、任务逻辑篇)

第二部分我们实现了程序在实模式DOS下的安装实测部分

课程设计2 详细实现(二、安装实测篇)

关于课设2的任务内容,我们已经全部完成,接下来我们来对这个程序的界面方面做一些优化

这一部分的内容不涉及软硬盘操作,因此没有要求在实模式下进行


1. 子界面文字说明

让我们给子功能界面加上标题与按键说明

1.1. 时钟显示

show_clock_title: db "Current time is:"
show_clock_func_str: db "'F1' to change color,'Esc' to return to main menu."
show_clock: ;时间显示

我们通过地址标号相减来计算字符串长度cx,将显示行数赋给ah,而后调用 mid_showstr

show_clock: ;时间显示
    ;寄存器压栈

    mov si,offset show_clock_title
    mov cx,offset show_clock_func_str-offset show_clock_title
    mov ah,10 ;显示在第10行
    call mid_showstr

    mov si,offset show_clock_func_str
    mov cx,offset show_clock-offset show_clock_func_str
    mov ah,16 ;显示在第16行
    call mid_showstr

show_clock_run: ;其它需要实时更新的操作,如:时间字符串显示,键入响应
    ......

效果展示:


如果希望标题与按键说明字符串也能实时发生颜色变化,可以将这两个字符串的显示操作放进循环运行的代码块当中:

    ......
show_clock_run: ;循环运行代码块,标题与按键说明字符串也进行实时更新
    mov si,offset show_clock_title
    mov cx,offset show_clock_func_str-offset show_clock_title
    mov ah,10 ;显示在第10行
    call mid_showstr

    mov si,offset show_clock_func_str
    mov cx,offset show_clock-offset show_clock_func_str
    mov ah,16 ;显示在第16行
    call mid_showstr

    ;显示时钟字符串、按键响应
    ......

效果展示:


1.2. 时钟设置

与上一个子程序的修改方法类似:

需注意字符串的长度不要算错

set_time_title: db "Input number string to modify the clock:"
set_time_func_str: db "'Backspace' to delete char, 'Enter' to finish modification."
set_time: ;设置时间
    push ax
    push cx
    push si

    mov si,offset set_time_title
    mov cx,offset set_time_func_str-offset set_time_title
    mov ah,10 ;显示在第10行
    call mid_showstr

    mov si,offset set_time_func_str
    mov cx,offset set_time-offset set_time_func_str
    mov ah,16 ;显示在第16行
    call mid_showstr

    ......

效果展示:


2. 光标处理

2.1. 字符串输入光标

我们发现时钟设置程序中输入字符串时没有光标提示,观感不是很好,因此我们来添加一个输入字符串末实时显示光标功能。

这里我们使用 int 10H中断例程的2号功能 置光标功能

参数:bh放页数,dh放行号,dl放列号

由于字符串在输入过程中长度不断改变,想要在字符串末实时显示光标,则需要获取字符串末的位置信息。我们可以想到,程序中的所有字符串显示操作都调用了 mid_showstr 子功能来完成,所以最容易获取字符串位置信息的方法应该是依靠这个子程序,那么我们就可以通过该子程序返回字符串末的位置信息来使用。


现在来为 mid_showstr 子程序添加一点逻辑:

;参数:行数ah,字符串首地址为 ds:si,长度为cx,最大为80,dh传入颜色属性
;返回值:ah返回行数,al返回字符串后一格列数,方便置光标
mid_showstr: ;字符串居行中显示
    push cx
    push si
    push di

⭐  push ax
    mov al,160
    mul ah ;行数
    mov di,80
    sub di,cx
    add di,ax ;根据字符串长度计算显示位置
    shr di,1
    shl di,1 ;对齐偶数单元地址

    cld

    mov byte ptr es:[di-2],' ' ;将字符串前一格置空格,至少保证执行一次,以清除最后一个字符
    jcxz short showstr_ret ;如果长度为0就不用显示
showstr_s:
    movsb
    mov es:[di],dh
    inc di
    loop showstr_s
    mov byte ptr es:[di],' ' ;将字符串后一格置空格

showstr_ret:
⭐⭐
    sub di,ax ;减去行数*160的偏移量
    shr di,1 ;/2计算当前di单元格所在列数
⭐  pop ax ;恢复行数ah
    sub al,al
    add ax,di ;al保存列数
⭐⭐
    pop di
    pop si
    pop cx
    ret

标注⭐的代码为修改部分,重点关注此处逻辑


可以想象,为了利用mid_showstr的返回值,我们会将置光标操作紧跟着mid_showstr使用:

    call mid_showstr ;打印字符串
    ;利用返回值坐标(ah,al)进行置光标操作

set_time执行打印字符串的操作后面添加上这一步:

set_time: ;设置时间
    ......

    mov ah,12 ;第12行
    call mid_showstr ;打印字符串

    ;利用返回值坐标(ah,al)赋值dh,dl置光标
    push dx ;dh还保存着颜色属性
    mov dx,ax 
    mov ah,2
    int 10H
    pop dx

    ......

注意:dh保存着全局颜色属性,修改后要及时恢复


2.2. 新的问题

测试一下。子程序中,光标能够显示在输入字符串末尾,这下合理多了^_^

但是!这里又产生了两个新的问题:

  • 当字符串输入到达最大长度时,光标还是显示在字符串的后面一格,我们却不能输入了;而我希望遇到这种情况时,光标能显示在最后一个字符的位置(光标前面那个'3'),表示已达最大输入

这当然不是什么大问题,但是我觉得 报看>_<

这个解决方式很简单,加上一个最大长度判断条件即可:

    mov ah,12 ;显示在第12行
    call mid_showstr ;打印字符串

    cmp cx,12 ;判断是否到达最大长度
    jne short set_cursor
    dec al ;此时al保存着将要设置光标的列数
set_cursor:
    ;利用返回值坐标(ah,al)赋值dh,dl置光标
    push dx ;dh还保存着全局颜色属性
    mov dx,ax
    mov ah,2
    int 10H
    pop dx

嗯,顺眼多了~


另一个问题就是:

  • 当字符串输入完毕返回主菜单时,发现光标竟然跑到天上来了

原本好好待在角落里倒还可以选择性忽略,但是现在这样……嗯,不能忍

让我们在每次显示主菜单的时候把光标扔到屏幕外边去

main_menu: ;主菜单
    call screen_clear ;清屏
    call menu_show ;显示菜单

    ;将光标置于屏幕外
    push dx
    mov dx,0ffffH ;超过最大行列数即可
    mov ah,2
    int 10H
    pop dx

    ......

好了,这下再看主菜单终于清爽多了~

然而,在虚拟机刚开机时还是无法避免光标的显示,这可能是CPU开机处理的流程原因,不过至少执行操作以后光标都能正常隐藏,因此就别在意这个小细节啦~


3. 完整代码

附上完整代码:

根据测试目的设置⭐处指令

assume cs:code

code segment
start:
⭐ ;jmp main ;直接测试任务程序就使用跳转指令,在实模式上执行安装操作可以忽略这条指令

    mov bx,cs
    mov es,bx

    mov bx,offset lead ;将引导程序写入软盘0道0面1扇区
    mov al,1 ;操作扇区数量
    mov ah,3 ;写入操作
    mov dl,0 ;驱动器号 软驱A
    mov dh,0 ;面号
    mov ch,0 ;磁道号
    mov cl,1 ;扇区号
    int 13H

    mov bx,offset main ;将主程序写入软盘0道0面2扇区开始的2个扇区
    mov al,2 ;操作扇区数量
    mov ah,3 ;写入操作
    mov dl,0 ;驱动器号 软驱A
    mov dh,0 ;面号
    mov ch,0 ;磁道号
    mov cl,2 ;扇区号
    int 13H

    mov ax,4c00H
    int 21H

lead: ;引导程序,被保存在软盘0道0面1扇区,由操作系统加载到 0:7c00H 处,负责被加载后从0道0面2扇区开始的2个扇区加载主程序
    sub bx,bx
    mov ss,bx
    mov sp,7f00H ;0:7e00H到0:7f00H是安全的栈空间
    
    push cs
    pop es
    mov bx,7f00H ;将主程序加载到 0:7f00H 处

    mov al,2 ;操作扇区数量
    mov ah,2 ;读取操作
    mov dl,0 ;驱动器号 软驱A
    mov dh,0 ;面号
    mov ch,0 ;磁道号
    mov cl,2 ;扇区号
    int 13H

    sub bx,bx
    push bx
    mov bx,7f00H
    push bx
    retf ;跳转到 0:7f00H 处开始执行主程序

org 7f00H ;防止数据标号错乱
main: ;主程序,被保存在软盘0道0面2扇区开始的2个扇区,由引导程序加载到 0:7f00H
    push cs
    pop ds ;数据标号是按代码段计算的,将数据段与代码段对齐
    mov ax,0b800H
    mov es,ax ;es固定指向显存段
    mov dh,70h ;白底黑字

    jmp short main_menu
    table dw reset_pc,start_system,show_clock,set_time ;子程序入口定址表
main_menu: ;主菜单
    call screen_clear ;清屏
    call menu_show ;显示菜单

    ;将光标置于屏幕外
    push dx
    mov dx,0ffffH ;超过最大行列数即可
    mov ah,2
    int 10H
    pop dx
    
    sub ah,ah
    int 16H ;等待输入
    ;ah返回键盘扫描码,al返回字符ASCII码
    
    cmp ah,1 ;Esc的扫描码
    je sret ;为了方便测试,我们令 输入Esc结束程序
    cmp al,'1'
    jb short main_menu ;无效输入
    cmp al,'4'
    ja short main_menu ;无效输入
    
    sub bh,bh
    mov bl,al
    sub bl,'1' ;直接从字符'1'的ASCII码上计算偏移量
    shl bx,1
    call screen_clear ;跳转子程序前先清屏
    call word ptr table[bx] ;选择跳转子程序
    jmp short main_menu ;子程序结束回到主菜单选择
sret:
    mov ax,4c00H
    int 21H
    
reset_pc: ;重启
    mov bx,0ffffH
    push bx
    sub bx,bx
    push bx
    retf ;跳转 ffff:0000H

start_system: ;引导现有操作系统
    sub bx,bx
    mov es,bx
    mov bx,7c00H

    mov al,1 ;操作扇区数量
    mov ah,2 ;读取操作
    mov dl,80H ;驱动器号 硬盘C
    mov dh,0 ;面号 0
    mov ch,0 ;磁道号 0
    mov cl,1 ;扇区号 1
    int 13H

    sub bx,bx
    push bx
    mov bx,7c00H
    push bx
    retf ;跳转 0000:7c00H

timestr: db 0,0,'/',0,0,'/',0,0,' ',0,0,':',0,0,':',0,0 ;时间字符串
CMOS_adr db 9,8,7,4,2,0 ;端口地址定址表
show_clock_title: db "Current time is:"
show_clock_func_str: db "'F1' to change color, 'Esc' to return to main menu."
show_clock: ;时间显示
    push ax
    push bx
    push cx
    push dx
    push si

    mov si,offset show_clock_title
    mov cx,offset show_clock_func_str-offset show_clock_title
    mov ah,10 ;显示在第10行
    call mid_showstr

    mov si,offset show_clock_func_str
    mov cx,offset show_clock-offset show_clock_func_str
    mov ah,16 ;显示在第16行
    call mid_showstr

show_clock_run: ;将压栈部分放在重复运行段外面
    sub bx,bx ;作为端口地址表的偏移量
    mov si,offset timestr ;这里使用地址标号是因为si不是字节类型
    mov cx,6
get_CMOS_s:
    mov al,CMOS_adr[bx]
    call get_CMOS ;在 ds:si 处写入CMOS在al地址处的数据
    inc bx
    add si,3
    loop get_CMOS_s

    mov si,offset timestr ;重新指向字符串开头
    mov ah,12 ;显示在第12行
    mov cx,17 ;时间字符串长度为17字节
    call mid_showstr ;居中显示

    mov ah,1
    int 16H ;int 16H 的 1 号功能:用来查询键盘缓冲区,对键盘扫描但不等待,并设置 ZF 标志位(0有输入,1无输入)
    je short show_clock_run ;无键盘输入,继续时钟模式

    sub ah,ah
    int 16H ;当且仅当有键盘输入,使用 int 16H 的 0 号功能获取输入信息
    
    cmp ah,1 ;Esc的扫描码
    je short show_clock_ret ;当输入为Esc,退出时钟模式

    cmp ah,3bH ;F1的扫描码
    ;cmp ah,1cH ;Enter的扫描码
    jne short show_clock_run ;其它为无效输入,不进行操作,继续时钟模式

    inc dh ;dh保存显示属性,当输入为F1,改变显示属性

    jmp short show_clock_run ;循环执行读取CMOS与显示
show_clock_ret:
    pop si
    pop dx
    pop cx
    pop bx
    pop ax
    ret

;参数:al传端口地址,ds:si 传写入地址
get_CMOS: ;从端口获取时间数据并写入 ds:si 处
    out 70H,al ;操作单元地址送入地址端口70H
    in al,71H ;将该单元从数据端口71H中读取到al
    mov ah,al
    and ah,1111b ;ah存时间数据低位(个位)
    shr al,1
    shr al,1
    shr al,1
    shr al,1 ;al存时间数据高位(十位)
    add ax,3030H ;转化为ASCII码
    mov [si],ax
    ret

clock_setstr: db 12 dup('0') ;时间设置字符串
set_time_title: db "Input number string to modify the clock:"
set_time_func_str: db "'Backspace' to delete char, 'Enter' to finish modification."
set_time: ;设置时间
    push ax
    push cx
    push si

    mov si,offset set_time_title
    mov cx,offset set_time_func_str-offset set_time_title
    mov ah,10 ;显示在第10行
    call mid_showstr

    mov si,offset set_time_func_str
    mov cx,offset set_time-offset set_time_func_str
    mov ah,16 ;显示在第16行
    call mid_showstr

    mov si,offset clock_setstr ;指向时间设置字符串
    sub cx,cx ;字符串初始长度为0

input_char: ;输入字符
    mov ah,12 ;显示在第12行
    call mid_showstr ;打印字符串

    cmp cx,12 ;判断是否到达最大长度
    jne short set_cursor
    dec al ;此时al保存着将要设置光标的列数
set_cursor:
    ;利用返回值坐标(ah,al)赋值dh,dl置光标
    push dx ;dh还保存着全局颜色属性
    mov dx,ax
    mov ah,2
    int 10H
    pop dx

    sub ah,ah
    int 16H ;监听键盘输入

    cmp al,'0'
    jb short not_digit
    cmp al,'9'
    ja short not_digit ;判断是否为数字

    call char_push ;添加字符并显示
    jmp short input_char ;下一轮输入
not_digit:
    cmp ah,0eH ;撤回键
    je short backspace
    cmp ah,1cH ;回车键
    je short enter
    jmp short input_char ;非法键,下一轮输入

backspace:
    call char_pop ;撤销字符并显示
    jmp short input_char ;下一轮输入

enter: ;结束字符串输入,修改CMOS并返回主菜单
    push bx
    sub bx,bx ;bx记录 CMOS_adr 偏移量
    mov cx,6
set_CMOS_s:
    push bx
    mov al,CMOS_adr[bx] ;可以复用 CMOS_adr 数据标号
    mov bx,[si]
    call set_CMOS ;在CMOS的al地址处写入bx数据
    pop bx
    inc bx
    add si,2
    loop set_CMOS_s
    pop bx

    pop si
    pop cx
    pop ax
    ret

;bx传时间ASCII码(bl存十位,bh存个位),al传端口号
set_CMOS: ;修改CMOS
    push bx
    out 70H,al
    sub bx,3030H ;ASCII码转BCD码
    shl bl,1
    shl bl,1
    shl bl,1
    shl bl,1 ;bl高位存十位
    or bl,bh ;bl低位存个位
    mov al,bl
    out 71H,al ;通过数据端口写入CMOS
    pop bx
    ret

;参数:al入栈字符,字符串长度首地址 ds:si,长度 cx
;返回值:字符串新长度 cx
char_push: ;字符入栈,指针后移,最多12位
    cmp cx,12
    je short char_push_ret ;最多12位

    push bx
    mov bx,cx
    mov [bx][si],al ;寻找当前指针所指向字符
    inc cx
    pop bx
char_push_ret:
    ret

;字符串长度首地址 ds:si,长度 cx
;返回值:al返回出栈字符,字符串新长度 cx
char_pop: ;字符出栈,指针前移,至少0位
    cmp cx,0
    je short char_pop_ret ;至少0位

    push bx
    mov bx,cx
    mov al,[bx][si] ;寻找当前指针所指向字符
    dec cx
    pop bx
char_pop_ret:
    ret

;参数:dh传入颜色属性
screen_clear: ;清屏
    push bx
    push cx
    push dx

    sub bx,bx
    mov dl,' ' ;dl 传入字符' ',dh 传入颜色属性
    mov cx,2000
clears:
    mov es:[bx],dx
    add bx,2
    loop clears

    pop dx
    pop cx
    pop bx
    ret

;参数:行数ah,字符串首地址为 ds:si,长度为cx,最大为80,dh传入颜色属性
;返回值:ah返回行数,al返回字符串后一格列数,方便置光标
mid_showstr: ;字符串居行中显示
    push cx
    push si
    push di

    push ax
    mov al,160
    mul ah ;行数
    mov di,80
    sub di,cx
    add di,ax ;根据字符串长度计算显示位置
    shr di,1
    shl di,1 ;对齐偶数单元地址

    cld

    mov byte ptr es:[di-2],' ' ;将字符串前一格置空格,至少保证执行一次,以清除最后一个字符
    jcxz short showstr_ret ;如果长度为0就不用显示
showstr_s:
    movsb
    mov es:[di],dh
    inc di
    loop showstr_s
    mov byte ptr es:[di],' ' ;将字符串后一格置空格

showstr_ret:
    sub di,ax ;减去行数*160的偏移量
    shr di,1 ;/2计算当前di单元格所在列数
    pop ax ;恢复行数ah
    sub al,al
    add ax,di ;al保存列数

    pop di
    pop si
    pop cx
    ret

line1 db "Press (1) - RESET PC"
line2 db "Press (2) - START SYSTEM"
line3 db "Press (3) - SHOW CLOCK"
line4 db "Press (4) - SET TIME"
lines dw line1,line2,line3,line4
lengths db line2-line1,line3-line2,line4-line3,lines-line4

;参数:通过dh传入字符颜色属性
menu_show: ;主菜单显示
    push ax
    push cx
    push si

    mov ah,15 ;将四个字符串分别显示到9、11、13、15行
    mov cx,4
menu_show_s:
    push cx ;调用 mid_showstr 前会修改cl,先把循环次数保存起来
    mov si,cx
    dec si ;通过循环次数计算偏移量
    mov cl,lengths[si] ;lengths为字节型数据组,使用cl保存长度
    shl si,1 ;注意数据类型,偏移量乘2
    mov si,lines[si] ;lines为字型数据组,使用si保存字符串首地址
    call mid_showstr

    sub ah,2 ;自下而上
    pop cx
    loop menu_show_s

    pop si
    pop cx
    pop ax
    ret

code ends
end start

4. 总结

这一篇文章的内容不是很多,仅作拓展选看用。

我们在之前任务程序的基础上优化了界面以及光标显示,总体来说我们的程序已经具备符合课设2任务要求的功能和较为清晰的使用界面,倘若还有其它改进想法就靠各位自己去发挥了(比如觉得白板界面太空旷,可以自己添加边框等等)。

这也是关于《汇编语言》课程设计2 系列的最后一篇文章,能将该课题做到如此完成度已经达到我自己的满意水平。在此过程中我追求代码简洁的同时尽量保证不失规范性,因而做了多次调整,如果发现前后代码不完全一致可能是因为没来得及同步所致,一切以本文的完整代码为准

参考材料:《汇编语言》(第4版)王爽

标签:课程设计,汇编语言,ah,mov,al,cx,push,bx,王爽
From: https://blog.csdn.net/m0_73327328/article/details/136932599

相关文章

  • 从高级语言到汇编语言(MIPS)
    从高级语言到汇编语言(MIPS)C语言是如何转化为汇编语言的?这一步在电脑中是由汇编程序完成的,但是了解C语言到汇编语言的转换过程有利于我们更好的编写出性能更加优异的程序,因此下面我将逐步介绍从C到MIPS的核心思想和实现步骤。一、存储结构核心:在MIPS中,所有的操作数必须来......
  • 汇编语言——实现用多种寻址方式在屏幕上显示“hello world !“
    ;数据段定义datasegment stringdb"helloworld!",0dh,0ah,'$' ;想要显示的字符个数,不显示$ countdw$-string-1dataends;堆栈段定义stacksegmentstack toplabelword dw30hdup(?)stackends;代码段定义codesegment ;assum伪指令,不执行 ass......
  • 计算机基础系列 —— 汇编语言
    Samehardwarecanrunmanydifferentprograms(Software)文中提到的所有实现都可以参考:nand2tetris_sol,但是最好还是自己学习课程实现一遍,理解更深刻。我们在之前的文章里,构建了Register、RAM和ALU,使得我们有了存储和计算的能力,我们接着借助之前的组合逻辑单元和时序......
  • 数据结构课程设计,用线性表做一个通讯录管理系统
    求一个注释,帮忙解析以下代码#include<iostream>#include<string>#include<cstdlib>#defineMAX2000usingnamespacestd;//通讯录管理系统//设计联系人结构体structPerson{ stringm_Name; intm_Sex; intm_Age; stringm_Phone; stringm_Addr;};//设计......
  • 汇编语言中的ORG指令
    一ORG指令ORG是Origin的缩写:起始地址,源。在汇编语言源程序的开始通常都用一条ORG伪指令来实现规定程序的起始地址。如果不用ORG规定则汇编得到的目标程序将从0000H开始。例如:ORG2000HSTART:MOVAX,#00H汇编语言源程序中若没有ORG伪指令,则程序执......
  • 课程设计——基于matlab语言的PCA人脸识别系统的设计与实现,采用GUI界面进行效果演示
    本论文源码是基于Matlab实现的PCA算法来进行人脸图片的识别与比对,通过GUI界面进行效果展示,适合基于matlab、人脸识别等人工智能领域的课程设计和毕设,整个算法结构简单、易于理解,如需完整源码,可以联系博主获取。一、引言人脸识别技术作为计算机视觉领域的一个重要分支,因其......
  • 汇编语言中的MVC
    一MVC指令1.移动字符串指令MVC移动字符串指令MVC的格式为:MVCD1(L,B1),D2(B2)(移动字符串)功能:(D1+(B1))←(D2+(B2))L个字符指令的执行用开始于D2(B2)的L字节替换开始于D1(B1)的L字节的内容。L个字节的内容每次改变一个,从左边开始。如果域不重叠的话,这一事实是不重要的,但......
  • C++ <atomic>汇编语言实现原理
    C++<atomic>汇编语言实现原理问题我们先看一下这段代码:/**badcnt.c-Animproperlysynchronizedcounterprogram*//*$beginbadcnt*//*WARNING:Thiscodeisbuggy!*/#include"csapp.h"void*thread(void*vargp);/*Threadroutineprototype*//*......
  • 《汇编语言》第3版 (王爽)实验7解析
    实验7解析实验7寻址方式在结构化数据访问中的应用解析:方法一:将每个数据项逐个进行传输,共有4个数据项(分别是年份、收入、雇员数、人均收入)人均收入需要计算后得到,每个数据项又有21个元素,所以需要传输21次assumecs:codedatasegment db'1975','1976','1977','1......
  • 汇编语言王爽
    第1章基础知识1.1机器语言机器语言是机器指令的集合,机器指令就是一堆二进制数字早期计算机就是执行机器指令,进行运算现在PC机种CPU(一种微处理器)会处理这些每一种微处理器,硬件设计和内部结构的不同,需要不同电平脉冲来控制,所以都有自己的机器指令集(机器语言)1.2汇编语言的......