汇编语言学习-课程设计2


课程设计2

题目描述

具体的描述就不再打一遍了

该程序的功能如下:

  1. 列出功能选项,让用户通过键盘进行选择,界面如下。
    实现页面
  1. 用户输入1, 重新启动计算机(提示FFFF:0)
  2. 用户输入2, 引导现有的操作系统
  3. 用户输入3, 执行动态显示当前日期、时间的程序。格式: 年/月/日 时:分:秒 F1改变 Esc键后,返回主菜单
  4. 用户输入4, 更改当前的日期、时间。返回主选单。

遇到的问题总结

这里面其实很多问题本可以避免,但是在看书的时候,并没有注意到这些东西,或者说注意到了,可没有正确的理解。课程设计正如书上面说的它用到了我们所学到的所有技术,对于我们的整个学习过程是具有总结性的。

这些问题在我写程序的时候,感觉很是问题,很有总结的必要。写完之后,倒是感觉简单了一些。

  1. ds:[offset A]offset A 的区别

    前面ds:[offset A]取的是具体地址里面的值,offset A 取得是偏移地址。在你写的时候,一定搞清楚,你到底是想要字符串的首地址还是字符串首地址那个字符

  2. 数据/代码结构的设置问题,设置怎样的数据结构会使程序结构更清晰更明了

    关于程序的逻辑具体就是把每一个菜单的总体逻辑放到一起,实现的细节写成子程序。

    菜单4的主体逻辑

  3. 多余代码废话的问题,很多程序写的很多余

    老毛病了,这个东西在以后也得多练。

  4. call指令和jmp指令\还有那些条件跳转的指令

    关于这个我有一点问题, 条件转移只有-127-128的范围,如果我想实现按照条件执行不同的函数,那我就得先条件跳转到一个子程序的地方。然后再call,执行完再jmp回来。

    有没有那种可以实现可以根据条件来call的, 但是这样一想的话,好像就多余了。因为依据现有的条件就可以实现,加上的话是不是就多余了呢。

  5. 寻址问题 ds:[bx+si]用数学的表达到底是什么样的,和你的写的是不是一样

    data

    本来是想着把ds弄成7E00,想着就不用在偏移量上面+7E00H了,结果发现自己是真脑残。正常的寻址过程是 段寄存器*16+偏移地址 用数学化的表示是 ax=ds*16+bx憨逼的你居然认为可以通过把ds设置为7E00H达到不用+7E00h的效果—真是太脑残了

  6. 子程序名称前后不一致

    第一开始并没有直接设计到软盘的操作,就是先实现页面和具体的功能。后面加上软驱的操作的时候,有些子程序的名字不一样了。也没改。就导致效果出不来,还得debug好长时间。

  7. 环境问题

    关于环境实在是人人都不一样,版本啊啥的,很容易就出现怎么弄,都弄不好的情况,但是总归来说还是不算太难。耐下心来取弄就行了。

    我看有的博主是直接用的winxp,但是我试过之后不行。于是我直接装了一个ms-dos的虚拟机,然后在xp系统中把汇编程序写进软驱中,再把软驱连接到ms-dos系统中,启动ms-dos系统。观察效果。

    在这里还是感谢一下博主,虽然在具体的问题上面还是得自己解决。但是最起码程序是可以运行的,让我看到了具体的效果。

编程上面的提升

  1. 根据功能号计算对应子程序在table中的偏移量。

    调用不同的功能

  2. 对于不规则的数据调用,可以先把要读取的不规则位置存放下来,用的时候按存放的顺序进行读取。这样就变不规则为规则了。

    变不规则为规则

  3. 如果觉得一个功能实现起来有些矛盾,可以去看看所有与之有关的程序。也许可以通过改已经实现的功能来解决。

    具体到这里就是:设计更改颜色,并没有在显示的地方进行设置。这样就避开了改变颜色之后,显示的程序又把颜色给覆盖掉的问题

  4. 关于debug的时候,有的时候会出现在运行循环之后,cpu那边的代码就给变化了

     其实造成这种奇奇怪怪原因的无非有以下几种情况:
     1.你在loop循环的过程中没有控制好临界条件
     2.在loop循环的过程中,最起码在判断cx的值的时候,cx的值发生了改变
     3.没有写对要写入的地址---地址搞错了等等
     4.累加变化的寄存器的值,累加的不对造成越界或者写错了
    

效果

重启

1

引导现有的系统

2

显示时间改变颜色返回

3

设置时间

4

代码

assume cs:code,ds:data,ss:stack

data segment
    db 256 dup(0)
data ends


stack segment
    db 128 dup(0)
stack ends

code segment
start:
    mov ax,data
    mov ds,ax
    
    mov ax,stack
    mov ss,ax
    mov sp,128

    mov ax,offset boot_end - offset boot
    call lead_to_softdisk;将引导程序写入软盘
    call boot_to_softdisk;将系统程序写入软盘

    mov ax,4c00h
    int 21h


;将系统程序从软盘读入内存
lead:
    ;设置栈
    mov bx,0
    mov ss,bx
    mov sp,7C00H

    ;将系统程序从软盘读入内存
    mov ax,0
    mov es,ax
    mov bx,7E00h
    ;int13入口参数 ah 2读  3写
    ;al 磁盘数| ch磁道号|cl扇区号|dh磁头号|dl驱动器号 0软驱A 80h盘C es:bx指向写入磁盘的数据
    mov al,2
    mov ch,0
    mov cl,2
    mov dh,0
    mov dl,0
    mov ah,2

    int 13h

    ;转到7E00H处执行
    mov ax,0
    push ax
    mov ax,7E00h
    push ax
    retf
ret    
lead_to_softdisk:
    push cs
    pop es
    mov bx,offset lead

    mov al,1
    mov ch,0
    mov cl,1
    mov dh,0
    mov dl,0
    mov ah,3

    int 13h
ret
boot_to_softdisk:
    push cs
    pop es
    mov bx,offset boot

    mov al,2
    mov ch,0
    mov cl,2
    mov dh,0
    mov dl,0
    mov ah,3

    int 13h
ret
boot:
    jmp bootstart
;*****************************************************************************
    MENU0 db 'Welcome to system!',0
    MENU1 db '1) reset pc',0      ;重新启动计算机
    MENU2 db '2) start system',0  ;引导现有的操作系统
    MENU3 db '3) clock',0         ;显示时间
    MENU4 db '4) set clock',0     ;设置时间
    TIME  db 'YY/MM/DD hh:mm:ss',0;时间显示格式
    SETTIMENOTE db 'Reset the time in the following format: YY/MM/DD hh:mm:ss. eg.21/11/2 10:49:50. Press Enter to end!',0;设置时间的提示
    SETTIME db '############',0;存放设置的时间
    CMOS  db 9,8,7,4,2,0          ;CMOS中日期格式的存放位置

    MENU    dw offset MENU0 - offset boot + 7E00h
            dw offset MENU1 - offset boot + 7E00h
            dw offset MENU2 - offset boot + 7E00h
            dw offset MENU3 - offset boot + 7E00h
            dw offset MENU4  -offset boot + 7E00h
            dw 0
        
    TABLE_FUNTION   dw offset m1-offset boot + 7E00h
                    dw offset m2-offset boot + 7E00h
                    dw offset m3-offset boot + 7E00h
                    dw offset m4-offset boot + 7E00h
;*****************************************************************************
bootstart:
    mov ax,0;本来是想着把ds弄成7E00,想着就不用在偏移量上面+7E00H了,结果发现自己是真脑残
    mov ds,ax;正常的寻址过程是 段寄存器*16+偏移地址 用数学化的表示是 ax=ds*16+bx
            ;憨逼的你居然认为可以通过把ds设置为7E00H达到不用+7E00h的效果---真是太脑残了

    call clearscreen
    call showmenu
    call keyboardinput
    
    cmp ah,02
    jb bootstart
    cmp ah,05
    ja bootstart
    
    mov bx,0
    sub al,31H
    mov bl,al
    add bx,bx
    call word ptr ds:[offset TABLE_FUNTION - offset boot + bx + 7E00h];子程序都存放到表里,按照偏移量来调用

    jmp bootstart;保证一直在循环里面
;==============================================================
;菜单一         重新启动计算机
m1:
    pushf
    mov ax,0FFFFH
    push ax
    mov ax,0
    push ax
iret
;==============================================================
;菜单二         引导现有的操作系统
m2:
    mov bx,0            ;将C盘(驱动80H)的0面0道0扇区复制到0:7C00H处
    mov es,bx
    mov bx,7C00H

    mov al,1    ;1个扇区
    mov ah,2    ;读
    mov ch,0    ;0磁道
    mov cl,1    ;1扇区
    mov dh,0    ;0面
    mov dl,80H  ;C盘驱动
    int 13H 

    call clearscreen;清屏
    mov bx,0            ;从0:7C00开始执行,启动引导操作系统
    push bx 
    mov bx,7C00H
    push bx
    retf
ret
;==============================================================
;菜单三         显示时间
m3: 
    call clearscreen;清屏
    m3_all_s:
        ;------这个地方还真挺离谱的,我把m3_gettime写到循环里就不行,写成子程序反而行了。。
        ;------至于为什么不行,我不清楚??下午看一下---又莫名其妙的可以了,我真服了
        ;------这种思维也挺重要的,菜单只显示菜单的基本逻辑,其他的全部弄到子程中。方便修改。思路也清楚
        ;获取时间
        call m3_gettime
        ;显示时间
        mov ax,0B800h
        mov es,ax
        mov si,10*160 + 30*2
        mov bx,0
        mov ds,bx                                       ;这种方式只是赋予了  TIME里面的YY
        ; mov bx,ds:[offset TIME - offset boot + 7E00h];仔细看看这种方式到底赋给bx的是什么,我知道你想给TIME的地址,但是
        mov bx,offset TIME - offset boot + 7E00h;真正应该这样写。真正要的是地址而不是值
        call showmenuline
        ;获取键盘输入
        ;------- call keyboardinput 这里使用这个获取键盘输入的话--也就是获取按键是个阻塞的过程,时间的显示会卡住
        in al,60h
        cmp al,01;Esc返回主页面
        je m3_backtostart
        cmp al,3BH;F1返回改变颜色
        je F1_m3_changecolor
        
        ;-------该是je的地方不能用call,否则程序执行就乱跳了。汇编中难道就没有那种根据条件来call的指令?
        ;-------就非得是je完之后再call,call完之后再跳回来??
    jmp m3_all_s
    
    m3_backtostart:
        ret

    F1_m3_changecolor:
        call m3_changecolor
        jmp m3_all_s
;名称: m3_changecolor
;功能: 改变屏幕的颜色
;参数: 无
;返回: 无
;应用举例: 
m3_changecolor:
    push bx
    push es
    push cx

    mov bx,0B800H
    mov es,bx
    mov bx,1
    mov cx,2000

    m3_changecolorloop:
        inc byte ptr es:[bx]
        add bx,2
    loop m3_changecolorloop

    pop cx
    pop es
    pop bx
ret
;名称: m3_gettime
;功能: 从CMOS中读取时间,将时间按照格式写入data段中的TIME中
;参数: 无
;返回: 无
;应用举例: 21/11/1 13:30:50
m3_gettime:
    push si
    push cx
    push bx
    ;往ds:TIME中写入
    mov si,0
    mov bx,0
    mov cx,6
    m3_gettime_s:
        push cx
        mov al,ds:[offset CMOS- offset boot + si + 7E00h]
        out 70h,al
        in al,71h

        mov ah,al
        mov cl,4 
        ;------造成代码在循环之后发生改变的,是这里改变了cx的值
        ;------其实造成这种奇奇怪怪原因的无非有以下几种情况:
        ;------1.你在loop循环的过程中没有控制好临界条件
        ;------2.在loop循环的过程中,最起码在判断cx的值的时候,cx的值发生了改变
        ;------3.没有写对要写入的地址---地址搞错了等等
        ;------4.累加变化的寄存器的值,累加的不对
        shr ah,cl;右移四位,取高四位
        and al,00001111b;取低四位
        
        add ah,30h
        add al,30h

        mov ds:[offset TIME - offset boot + bx + 7E00h],ah
        mov ds:[offset TIME - offset boot + bx + 1 + 7E00h],al
        
        add bx,3
        add si,1
        pop cx
    loop m3_gettime_s
    
    pop bx
    pop cx
    pop si
ret
;==============================================================
;菜单四         设置时间---回车结束
m4:
    push bx
    push dx
    push cx
    ;清屏
    call clearscreen
    ;显示提示
    mov ax,0b800h
    mov es,ax
    mov si,10*160
    mov bx,0
    mov ds,bx
    mov bx,offset SETTIMENOTE- offset boot + 7E00h
    call showmenuline
    
    ;放置光标
    mov ah,2
    mov bh,0
    mov dh,13
    mov dl,30
    int 10h

    ;接受输入,设置CMOS时间
    call getstr
    call settimetocmos

    ; 光说不返回到主菜单呢,你看看哪里有start?! 早就改成bootstart了
    ; ;返回主菜单
    ; m4_backtostart:
    ;     call start

    pop cx
    pop dx
    pop bx
ret
;==============================================================
;将设置好的时间写入CMOS中
settimetocmos:
    push bx
    push cx
    push dx
    push si
    mov si,offset SETTIME- offset boot + 7E00h
    mov cx,6
    mov bx,0
    mov ds,bx
    settimetocmos_s:
        push cx

        mov word ptr dx,[si]
        sub dx,3030H
        mov cl,4
        shl dl,cl
        and dh,00001111B
        or dl,dh;从两个ACII码---16位转换成BCD码--8位

        mov al,ds:[offset CMOS- offset boot + bx + 7E00h]
        out 70H,al    ;将al送入地址端口70h
        mov al,dl
        out 71H,al ;    将数据写入CMOSRAM时钟
        
        add bx,1
        add si,2

        pop cx
    loop settimetocmos_s
    pop si
    pop dx
    pop cx
    pop bx
ret
;==============================================================
;字符串接收
getstr:
    push ax
    mov si,offset SETTIME - offset boot + 7E00h
    getstrs:
        mov ah,0
        int 16h
        cmp al,30h      ; ASCII码小于30h,说明不是数字
        jb nonumber
        cmp al,39H
        ja nonumber     ; ASCII码大于39h,也不是数字
        mov ah,0
        call charstack  ; 字符入栈
        mov ah,2
        call charstack  ; 显示栈中的字符
        jmp getstrs
    nonumber:
        cmp ah,0eh      ; 退格键的扫描码
        je backspace    
        cmp ah,1ch      ; Enter键的扫描码
        je enter
        cmp ah,01h      ; Esc键的扫描码
        je getstrs_backtostart
        jmp getstrs
    backspace:
        mov ah,1
        call charstack  ; 字符出栈
        mov ah,2
        call charstack  ; 显示栈中的字符
        jmp getstrs
    enter:              ; 输入回车之后也直接推出去
        pop ax
ret
    getstrs_backtostart: ;esc返回主菜单
        jmp bootstart
;==============================================================
;名称: keyboardinput
;功能: 使用int中断获取键盘的输入
;参数: 无
;返回: ah为扫描码   al为ASCII码
keyboardinput:
    mov ax,0
    int 16h
ret
    
;==============================================================
;名称: clearscreen
;功能: 清除显存中第一页的显示
;参数: 无
;返回: 无
clearscreen:
    push bx
    push es
    push di
    push cx

    mov bx,0B800H
    mov es,bx
    mov di,0

    mov bx,0700h;全部设置为黑色,填充就是0 设置颜色默认为黑底白字
    mov cx,2000

    clearscreenloop:	
            mov es:[di],bx
            add di,2
    loop clearscreenloop
    
    pop cx
    pop di
    pop es
    pop bx
ret
;==============================================================
;名称: showmenu
;功能: 展示菜单
;参数: 无
;返回: 无
showmenu:
    push si
    push bx
    push di
    push es
    push cx

    mov bx,0B800H   ;显存的位置
    mov es,bx
    mov si,10*160 + 30*2

    ;mov bx,offset MENU - offset boot + 7E00h;------就得像这样先把MENU转移出来才行。。。。ds:[ds:MENU[bx]+di]这种写法也可以
    ;上面这样算出来是标量啊,肯定是显示不了第一句话。你真正要的是ds:[bx]这样才是第一句的地址
    ;所以在你把循环改了之后,就可以显示第一句了 
    mov di,0

    mov cx,5
    showmenu_s:
        mov bx,ds:[offset MENU - offset boot + di + 7E00h];每一个字符串所对应的首地址
        call showmenuline
        add si,160
        add di,2
    loop showmenu_s

    showmenu_s_ret:
        pop cx
        pop es
        pop di
        pop bx
        pop si
ret

;名称: showmenuline
;功能: 显示字符串
;参数: ds:bx指向要显示字符串的首地址,以0结尾
;      es:si 写入显存的位置
;返回: 无
showmenuline:;ds:bx指向要显示的字符串的首地址,以0结尾,si标明位置
    push bx
    push si
    showmenuline_s:
        mov al,ds:[bx]
        cmp al,0
        je showmenuline_s_ret
        
        mov es:[si],al
        ; mov byte ptr es:[si+1],07H 在清屏的地方已经设置过了
        
        add si,2
        add bx,1
    jmp showmenuline_s

    showmenuline_s_ret:
        pop si
        pop bx
ret
;==============================================================
;子程序: 字符栈的入栈、出栈和显示
;参数说明: ah=功能号 0入栈 1出栈 2显示
;         ds:si指向字符栈空间
;         对于0号功能:al=入栈字符
;         对于1号功能:al=返回字符
;         对于2号功能:dh\dl=字符串在屏幕上显示的行、列位置
charstack:
    jmp short charstart

    table dw offset charpush- offset boot + 7e00h,offset charpop - offset boot + 7e00h,offset charshow - offset boot + 7e00h
    top dw 0                        ;the top of stack

    charstart:
        push bx
        push cx
        push di
        push es
        push si

        mov bx,0
        mov ds,bx

        cmp ah,2
        ja sret
        mov bl,ah
        mov bh,0
        add bx,bx
        jmp word ptr ds:[offset table - offset boot + bx + 7E00h]

    charpush:
        mov cx,ds:[offset top - offset boot + 7E00h]
        cmp cx,11
        ja sret
        mov bx,ds:[offset top - offset boot + 7E00h]
        mov ds:[si][bx],al
        inc cx
        mov ds:[offset top - offset boot + 7E00h],cx 
        jmp sret

    charpop:
        mov cx,ds:[offset top - offset boot + 7E00h]
        cmp cx,0
        je sret
        dec cx
        mov ds:[offset top - offset boot + 7E00h],cx 
        mov bx,ds:[offset top - offset boot + 7E00h]
        mov al,ds:[si][bx]
        mov byte ptr ds:[si][bx],'#'
        jmp sret

    ;-------这里实在是没办法了,逻辑上都没有问题,用子程序显示就能显示,用书上的程序反而不显示,不知道为什么
    charshow:
        mov bx,0b800h
        mov es,bx
        mov si,13*160+30*2
        mov bx,offset SETTIME - offset boot + 7E00h
        call showmenuline
    sret:
        pop si
        pop es
        pop di
        pop dx
        pop bx
ret
;==============================================================
boot_end:nop
;================================================================
code ends
end start

文章作者: 美食家李老叭
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 美食家李老叭 !
评论
  目录