课程设计2
题目描述
具体的描述就不再打一遍了
该程序的功能如下:
- 列出功能选项,让用户通过键盘进行选择,界面如下。
- 用户输入1, 重新启动计算机(提示FFFF:0)
- 用户输入2, 引导现有的操作系统
- 用户输入3, 执行动态显示当前日期、时间的程序。格式: 年/月/日 时:分:秒 F1改变 Esc键后,返回主菜单
- 用户输入4, 更改当前的日期、时间。返回主选单。
遇到的问题总结
这里面其实很多问题本可以避免,但是在看书的时候,并没有注意到这些东西,或者说注意到了,可没有正确的理解。课程设计正如书上面说的它用到了我们所学到的所有技术,对于我们的整个学习过程是具有总结性的。
这些问题在我写程序的时候,感觉很是问题,很有总结的必要。写完之后,倒是感觉简单了一些。
ds:[offset A]
和offset A
的区别前面
ds:[offset A]
取的是具体地址里面的值,offset A
取得是偏移地址。在你写的时候,一定搞清楚,你到底是想要字符串的首地址
还是字符串首地址那个字符
数据/代码结构的设置问题,设置怎样的数据结构会使程序结构更清晰更明了
关于程序的逻辑具体就是把每一个菜单的总体逻辑放到一起,实现的细节写成子程序。
多余代码废话的问题,很多程序写的很多余
老毛病了,这个东西在以后也得多练。
call指令和jmp指令\还有那些条件跳转的指令
关于这个我有一点问题, 条件转移只有-127-128的范围,如果我想实现按照条件执行不同的函数,那我就得先条件跳转到一个子程序的地方。然后再call,执行完再jmp回来。
有没有那种可以实现可以根据条件来call的, 但是这样一想的话,好像就多余了。因为依据现有的条件就可以实现,加上的话是不是就多余了呢。
寻址问题 ds:[bx+si]用数学的表达到底是什么样的,和你的写的是不是一样
本来是想着把ds弄成7E00,想着就不用在偏移量上面+7E00H了,结果发现自己是真脑残。正常的寻址过程是
段寄存器*16+偏移地址 用数学化的表示是 ax=ds*16+bx
憨逼的你居然认为可以通过把ds设置为7E00H达到不用+7E00h的效果—真是太脑残了子程序名称前后不一致
第一开始并没有直接设计到软盘的操作,就是先实现页面和具体的功能。后面加上软驱的操作的时候,有些子程序的名字不一样了。也没改。就导致效果出不来,还得debug好长时间。
环境问题
关于环境实在是人人都不一样,版本啊啥的,很容易就出现怎么弄,都弄不好的情况,但是总归来说还是不算太难。耐下心来取弄就行了。
我看有的博主是直接用的winxp,但是我试过之后不行。于是我直接装了一个ms-dos的虚拟机,然后在xp系统中把汇编程序写进软驱中,再把软驱连接到ms-dos系统中,启动ms-dos系统。观察效果。
在这里还是感谢一下博主,虽然在具体的问题上面还是得自己解决。但是最起码程序是可以运行的,让我看到了具体的效果。
编程上面的提升
根据功能号计算对应子程序在table中的偏移量。
对于不规则的数据调用,可以先把要读取的不规则位置存放下来,用的时候按存放的顺序进行读取。这样就变不规则为规则了。
如果觉得一个功能实现起来有些矛盾,可以去看看所有与之有关的程序。也许可以通过改已经实现的功能来解决。
具体到这里就是:设计更改颜色,并没有在显示的地方进行设置。这样就避开了改变颜色之后,显示的程序又把颜色给覆盖掉的问题
关于debug的时候,有的时候会出现在运行循环之后,cpu那边的代码就给变化了
其实造成这种奇奇怪怪原因的无非有以下几种情况: 1.你在loop循环的过程中没有控制好临界条件 2.在loop循环的过程中,最起码在判断cx的值的时候,cx的值发生了改变 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