王爽 汇编语言 课程设计2

;本程序在VMWARE中调试通过,可以写入软盘,并正常执行
;可以使用Bochs 但是操作细节比较复杂
;程序界面不够友好
;程序的切换方式是 ESC 注意不要进入时间设置就退不出来了.
;在时间设置界面没有设置提示语句,进去就是黑屏幕,不过只要输入数字就会显示.注意输入 的
;数字的大小,世界上没有24:00:00以后的时间.我也懒得去判断输入了.
;输入后按回车或者超过6个有效 数字,程序会自动设置时间,并切换回主界面
;在Windows 下看不到时间的更改,原因是,Windows只在系统引导的时候读取CMOS时间,并把它的
;初值给8253,其余时间都由8253为操作系统提供时钟,因此尽管在Windows状态下可以更改CMOS
;中的时间,但是右下脚的时间是不会产生变化的,也就是说更改时间是看不到的.关机的时候,
;系统把8253中的时间经过计算后写回CMOS。这点在WINDOWS下调试程序应当注意.

;在Windows 2000以后的操作系统中,系统内核不允许直接端口操作,因此,在DEBUG中执行IN,OUT
;操作是无效的.因此端口的调试应在9X或DOS下进行,比如在虚拟机中的Windows 98中.
;中断嵌套问题。
;以一个8253作为中断处理芯片来算,则同时可能发生的最大中断数量为8级,因此堆栈必须至少足够
;8个以上中断的最大数目才不会发生可能的问题。
;286 以上系统使用两片8259A级联管理15级可屏蔽中断。从中断0到中断15。比较常见的分配方式:
;       IRQ0  系统定时器
;      IRQ1  键盘
;      IRQ2  可编程中断控制器8259A
;      IRQ3  COM2 ( 串口 )
;      IRQ4  COM1 ( 串口 )
;      IRQ6  软盘控制器
;      IRQ7  并行口LPT1
;      IRQ8  系统CMOS/实时钟
;      IRQ12  PS/2鼠标
;       IRQ13  数学协处理器
;      IRQ14  第一IDE控制器(硬盘)
;      IRQ15  第二IDE控制器  (CDROM ) 
;                        IRQ5    可用     (如声卡) 
;                        IRQ9    可用     (如网卡)
 ;                       IRQ10  可用     (如USB)
 ;                       IRQ11  可用     (如SCSI主适配器)
;按照16级中断来处理,那么,在极端的情况下如果16级同时发生,则事先准备的堆栈区域必须满足
;16级中断的要求。每级中断至少14个寄存器。即14*16/8*16=448B, 堆栈应大于此数.

;CPU工作在 8086模式下,BIOS在初始化完系统后,在1M内存的使用情况

;384KB上位内存----- ************************
;                             *                                        * 
;                             *                                        * 
;保留                       *                                        * 
;                             *                                        *
;                             *                                        *
;640KB常规内存----- ************************------9FFFFH 
;                             *                                        * BIOS扩展数据区 1KB
;                             ************************------9FC00H
;                             *                                        * 
;                             *                                        *  ___607KB
;                             *                                        * 
;                             *                                        * 
;                             ************************------07E00H
;                             *                                        * 系统引导扇区安放位置 512B
;                             ************************ ------07C00H
;                             *                                        * 
;                             *                                        * _______29KB
;                             *                                        * 
;                             ************************------00800H
;                             *                                        *  BIOS数据区 1KB
;                             ************************------003FFH
;                             *                                        *  BIOS中断向量表 1KB
;                             ************************------00000H
;
;只可以使用常规的640K中的没有被占用的空间。

Assume cs:code
Data Segment
Error03 db 'Disk Write-Protect Error','$' ;磁盘写保护错误
Error04 db 'Can Not Find The Right Sector','$'    ;磁道寻找错误,软盘损坏
Error20 db 'Floppy Drive Error ,Maybe It Can Not Work !','$'  ;软盘不存在或工作部正常
Error80 db 'No Answer From Disk Drive!','$' ;驱动器不响应
ErrorExit db 'Press Any Key To Try Again Or Press ESC To Quit','$'
Data  Ends

Code Segment
Start:

;安装程序
;写入以下的程序到第一个和后面的扇区
;不需要确定程序的确切固定长度,因为int 13h只能按照512字节的倍数传递,所以即使比512多
;一个字节,也要传递512个。
mov ax,Data
mov ds,ax

mov ax,cs
mov es,ax   ;要写的内存基址
lea bx,Real ;要写的内存偏移

mov ah,3   ;功能号,写
mov al,((ProgrameEnd-Start)/512+1);写扇区的数目,浪费一个扇区以获得编写的方便。
mov ch,0   ;磁道号
mov cl,1   ;扇区号
mov dl,0   ;驱动器号
mov dh,0   ;磁头号(面)
int 13h   

;磁盘读写容错处理
cmp ah,0
je WriteFinish

cmp ah,03h
je WriteError03

cmp ah,04h
je WriteError04

cmp ah,20h
je WriteError20

cmp ah,80h
je WriteError80

WriteFinish:
mov ah,4ch
int 21h

WriteError03:
mov si,Offset Error03
jmp WriteErrorManage

WriteError04:
mov si,Offset Error04
jmp WriteErrorManage

WriteError20:
mov si,offset Error20
jmp WriteErrorManage

WriteError80:
mov si,Offset Error80
jmp WriteErrorManage

WriteErrorManage:
mov dh,4
mov dl,4
call near ptr Show_Str
mov si,Offset ErrorExit
mov dh,6
mov dl,4
Call near ptr Show_Str
mov ah,0
int 16h
Call near ptr ClearScrean
cmp al,1bh
jne Start
jmp WriteFinish

;实际程序体
Real:
;第一个扇区的数据
;第一个扇区只作为引导第二个扇区的引导程序 作用是把第二个扇区读到0:800处,
;并置cs:ip到0:800,原因是在引导现有的操作系统的时候,需要用到0:7c00内存区
;其中int 9h 需要占用0040:17作为状态字
;必须把0:7c00内存空闲出来。
;由于硬盘引导记录只能被存放到0:7c00处才能被正确引导,因此,需要在引导前把本程序
;移出内存。
;程序转移

;设置系统用堆栈内存区,由于没有操作系统支持,因此必须自己设定堆栈范围
;栈顶设置在9FBFF:0处,9FC00H(BIOS)保留数据区前一个位置.

cli ;避免堆栈设置时发生中断
mov ax,9000H             
mov ss,ax
mov ax,0FBFFH
mov sp,ax
sti

;内存基址设置,一次设置,以后不用更改,因为程序小于64K
mov ax,0
mov ds,ax
mov es,ax  

;读取第二和以后的扇区到内存的0:800处
mov bx,800h ;要读的内存偏移
mov ah,2   ;功能号,读
mov al,((ProgrameEnd-T)/512+1)   ;读扇区的数目,浪费512个字节以获得编写的方便。
mov ch,0   ;磁道号
mov cl,2   ;扇区号
mov dl,0   ;驱动器号
mov dh,0   ;磁头号(面)
int 13h   

;置CS,IP
mov ax,0
mov bx,800h
push ax
push bx
retf ;retf 相当于
     ;pop ip 
     ;pop cs 
     ;cs ip 的值不能通过普通的mov指令修改

;凑足一个扇区,主要满足int 19 对'55AA'标志的要求
;当前指令减去标号指令地址来计算应当的填充数据.
 db 510-($- Real) dup( 'A')

DW  0AA55H

;一扇区以后的扇区的数据
T:
jmp ReturnPoint;实际程序代码区
;Data Segment
Reset        db '1)Reset PC' ,0ah,0ah,0ah ;换行               ;'$'
StartS       db '2)Start System',0ah,0ah,0ah             ;'$'
Clock        db '3)Clock',0ah,0ah ,0ah                ;'$'
Set          db '4)Set Clock','$'
;时钟设置界面提示信息
SetTimeMessage db 'Please Input The Right Time :','$'

TimeBuffer db 3 dup(0),'e'    
                ;'$'的ASCII为36 即 0x24 会出现错误BCD 码中有0x24 e的ASCII为101 即 0x65   
               ;判断是否为终止符号
Counter dw 0 ;用在时钟设置中的字符输入计数器
TimeShowBuffer db 6 dup('0'),'$'
;Data ends

ReturnPoint: ;程序返回点
;设置屏幕显示
;清屏
call ClearScrean
;由于使用静态地址分配,因此只有采用标号差来计算距离数据区的长度,最终确定数据区的位置。
mov si,(Reset-T+800H)
mov dh,4
mov dl,12
call Show_Str

;循环读取键盘缓冲区
ReadKeybordBuffer:
mov ax,0 ;实际参数传递为ah ,但要在循环读取中要清空 al 因此直接清空 ax
int 16h
cmp al,'1'
je RebootFar ;程序超过JE的最大跳转范围
cmp al,'2'
je  StartSystemFar
cmp al,'3'
je TimeShow
cmp al,'4'
je Clock_SetFar
jmp ReadKeybordBuffer

Clock_SetFar:
jmp Clock_Set
RebootFar:
jmp Reboot
StartSystemFar:
jmp StartSystem

;时间显示
TimeShow:
call ClearScrean

;读取时间
TimeShowStart:
mov bx,(TimeBuffer-T+800H)
call ReadTime

;转换时间
mov bx,(TimeBuffer-T+800H)
mov si,(TimeShowBuffer-T+800H);偏移地址
call BCDChange

;向显示时间的格式转换
mov bx,(TimeShowBuffer-T+800H)
mov si,bx
call ChangeForShow
;屏幕显示
mov si,(TimeShowBuffer-T+800H)
mov dh,16
mov dl,20
call Show_Str

;检查键盘输入
mov ah,01h;检查缓冲区,若有字符则ZF=0,否则ZF=1 同时 AH=扫描码,AL=ASCII
int 16h ;在调用int 16h的过程中共需要多次PUSH数据,因此堆栈应满足需要(消耗100字节以内的内存)。 
jnz CheckCharacter
jmp TimeShowStart
CheckCharacter:
mov ah,00h
int 16h
cmp ah,01h ;判断是否为ESC
je ExitTimeShow
jmp TimeShowStart
ExitTimeShow:
call ClearScrean ;清屏

jmp ReturnPoint

Clock_Set:
;清屏
Call ClearScrean

mov si,(SetTimeMessage-T+800H)
mov dh,3
mov dl,6
Call Show_Str

;获取TimeShowBuffer的偏移地址作为字符串输入的接收内存
mov si,(TimeShowBuffer-T+800H)

;清除切换界面后内存的数据残留问题
mov ax,0
mov [si-2],ax
mov al,'0'
mov bx,0
mov cx,6
ClearDump:
mov [si][bx],al
inc bx
loop ClearDump

;字符输入循环控制部分
CharInputControl:
;16号中断获取字符
mov ah,0
int 16h

;判断是否是数字
;判断是否小于0
cmp al,'0'
jnb next

;非数字则判断是否为控制字符
jmp ControlCharInput 
;判断是否大于9
next:
cmp al,'9'

;数字则执行数字的输入
jna NumberCharInput

;非数字则判断是否为控制字符
jmp ControlCharInput

;控制字符判断
ControlCharInput:
;判断是否为左删除键
cmp al,08h
je BackSpace

;判断是否为回车
cmp al,0dh
je SetTimeNow

;判断是否为ESC
cmp al,1bh
je ReturnPointLong

;返回字符输入
jmp CharInputControl

ReturnPointLong:
jmp ReturnPoint

;是有效的数字输入
NumberCharInput:

;判断是否超过6个数字的输入
mov dx,[si-2]
cmp dx,5
ja  SetTimeNow

;数字写入相应得内存区域
mov ah,0
;程序超过CALL最大长度,要使用CALL far PRT或者CALL near PRT 实现跳转,
;但是这样长跳并不能解决所有问题。
;因为长跳是CS IP入栈 并重新设置编译器计算的地址,而本程序的地址不能通过编译器计算,
;此时的call 和JMP 有很大的差别.
;只能实行近跳(Near)
;注意JUMP FAR PTR 和jump near ptr 的区别很大,尤其在静态地址分配问题上.
call near ptr CharactorInputControl

;显示刚才的输入数字
mov ah,2
mov dh,9
mov dl,11
Call near ptr CharactorInputControl
;返回字符输入
jmp CharInputControl

;处理左删除
BackSpace:

;字符出栈
mov ah,1
call near ptr CharactorInputControl
;屏幕显示出栈后剩余的字符
mov ah,2
mov dh,9
mov dl,11
call near ptr CharactorInputControl
;返回字符输入
jmp CharInputControl

;时间设置开始
SetTimeNow:

;设置偏移
mov si,(TimeShowBuffer-T+800H)
mov bx,si
call near ptr ChangeFromKeyboardInput

mov si,(TimeShowBuffer-T+800H)
mov bx,(TimeBuffer-T+800H)
call near ptr ChangeToBCD

mov si,(TimeBuffer-T+800H)
call near ptr WriteToCMOS

jmp ReturnPoint

;屏幕显示子程序,以'$'为结束标志
;以ds:si指向要显示的内存区,dh存放行,dl存放列
Show_Str proc near

;寄存器入栈保护
push es
push ax

push bx
push cx

mov ax,0b800h
mov es,ax
mov al,160
mul dh
mov bx,ax
mov dh,0
add dl,dl
add bx,dx
mov cx,bx ;为换行标志准备
LoopShow_Str:
;判断结束标志
cmp ds:[si],byte ptr '$'
je EndLoopShow_Str
;判断换行标志
cmp byte ptr ds:[si], 0ah
jne NoLF

add cx,160
mov bx,cx 
inc si
jmp LoopShow_Str

NoLF:
mov al,ds:[si]
mov es:[bx],al
add bx,2
inc si
jmp LoopShow_Str

EndLoopShow_Str:
pop cx
pop bx
pop ax
pop es
ret

Show_Str endp

;BCD码向十进制转换得子程序
;ds:[bx]指向要处理的数据的内存地址
;es:[si]指向处理完成后数据存放的地址
;'$'指示处理内存的数据的终止符号。
BCDChange proc near
;寄存器保护
push ax
push cx

BCDChangeStart:
mov al,ds:[bx]
cmp al,'e'     ;'$'的ASCII为36 即 0x24 会出现错误 e的ASCII为101 即 0x65   
               ;判断是否为终止符号
je ExitBCDChange

mov ah,al   ;保护AL
mov cl,4    ;设置移动位数
;高四位,并存储
shr ah,cl
mov es:[si],ah
inc si

;低四位
;清高四位
shl al,cl
shr al,cl

mov es:[si],al
inc si
inc bx
jmp BCDChangeStart

ExitBCDChange:

pop cx
pop ax
ret
BCDChange Endp

;数字向屏幕显示转化子程序
;ds:[bx]指向要处理的数据的内存地址
;es:[si]指向处理完成后数据存放的地址
;'$'指示处理内存的数据的终止符号。
ChangeForShow proc near
push ax

ChangeForShowStart:
mov al,ds:[bx]
cmp al,'$'
je ExitChangeForShow
add al,30h
mov es:[si],al
inc bx
inc si
jmp ChangeForShowStart

ExitChangeForShow:
pop ax
ret
ChangeForShow  Endp

;时间读取子程序
;ds:[bx]指向存放数据的内存地址
ReadTime proc near
push ax
;时
mov al,4
out 70h,al
in al,71h
mov ds:[bx],al 
inc bx
;分
mov al,2
out 70h,al
in al,71h
mov ds:[bx],al
inc bx
;秒
mov al,0
out 70h,al
in al,71h
mov ds:[bx],al
pop ax
ret
ReadTime Endp

;清屏子程序
ClearScrean proc near
push ax
push bx
push cx
push ds
mov ax,0b800h
mov ds,ax
mov cx,2000
mov bx,0
loopClearScrean:
mov ds:[bx],byte ptr ' '
add bx,2
loop loopClearScrean
pop ds
pop cx
pop bx
pop ax
ret 
ClearScrean Endp

;转化用户的键盘输入到实际的数据输入的子程序
;ds:[si]指向要转化的内存区于
;ES:[bx]指向转化完成后的内存地址
;用'$'作为结束的标志
ChangeFromKeyboardInput proc near
push ax
StartChangeFromKeyboardInput:
mov al,ds:[si]
cmp al,'$'
je ExitChangeFromKeyboardInput
sub al,30h
mov es:[bx],al
inc si
inc bx
jmp StartChangeFromKeyboardInput
ExitChangeFromKeyboardInput:
pop ax

ret
ChangeFromKeyboardInput Endp

;十进制向BCD码转化的子程序
;ds:[si]指向要处理的内存区域用'$'作为内存结束标志
;es:[bx]指向处理完的内存区域
ChangeToBCD proc near

push cx
push ax
mov cl,4
StartChangeToBCD:
mov al,ds:[si]
cmp al,'$'
je ExitChangeToBCD
shl al,cl
inc si
mov ah,ds:[si]
add al,ah
inc si
mov es:[bx],al
inc bx
jmp StartChangeToBCD
ExitChangeToBCD:
pop ax
pop cx
ret

ChangeToBCD Endp

;数据输入和屏显子程序
;ah存放功能号,0表示入栈,1表示出栈,2表示显示
;ds:[si]指向字符栈空间
;ds:[si]的前面两个字节用于存放计数器的数值
;对于0号功能:(al)=入栈字符
;对于1号功能:(al)=返回的字符
;对于2号功能:(dh)、(dl)=字符在屏幕上显示的行、列位置
CharactorInputControl proc near
push bx
push dx
push di
push es

cmp ah,0
je charpush
cmp ah,1
je charpop
cmp ah,2
je charshow
jmp sret

charpush:
mov bx,[si-2]
mov [si][bx],al
inc word ptr [si-2]
jmp sret

charpop:
cmp word ptr [si-2],0
je sret
dec word ptr [si-2]
mov bx,[si-2]
mov al,[si][bx]
mov byte ptr [si][bx],0  ;清除数据
jmp sret

charshow:
mov bx,0b800h
mov es,bx
mov al,160
mov ah,0
mul dh
mov di,ax
add dl,dl
mov dh,0
add di,dx
mov bx,0

charshows:
cmp bx,[si-2]
jne noempty
mov byte ptr es:[di],' '
jmp sret
noempty:
mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+2],' '
inc bx
add di,2
jmp charshows

sret:
pop es
pop di
pop dx
pop bx
ret

CharactorInputControl Endp

;数据写入CMOS
;ds:[si]指向内存区域
;CPU速度太快,端口来不及取走数据,因此,必须设置CPU等待。
;不等待的话,总是设置错误,调了好几天才注意到.
;究竟等待多长,不清楚,因此干脆写入一个数据后,就一直从端口读出,和写入的比较,直到相同了,
;才设置下一个.
;数据从端口的读入读出时间正好使得CMOS有时间取走数据。
WriteToCMOS proc near
push ax
;时
mov al,4
out 70h,al
mov al,ds:[si]
mov ah,al
out 71h,al

;延时代码,CPU从端口取走数据会自动匹配端口速度,因此下列代码相当于延时,同时又提高了
;程序的执行速度和代码的简化
in al,71h
inc si

;分
mov al,2
out 70h,al
mov al,ds:[si]
mov ah,al
out 71h,al
inc si

in al,71h

;秒
mov al,0
out 70h,al
mov al,ds:[si]
out 71h,al
in al,71h
pop ax
ret
WriteToCmos Endp

Reboot:

mov ax,0ffffh
mov bx,0
push ax
push bx
retf ;retf 相当于
     ;pop ip 
     ;pop cs 
     ;cs ip 的值不能通过普通的mov指令修改

StartSystem:

;读取C盘的第一扇区到内存的0:7c00处

             mov bx,7c00h  ;读到内存中的地址。

             mov dl,80h
             mov ah,2     ;读磁盘操作     
             mov dh,0     ;0盘面
             mov ch,0     ;0磁道
             mov cl,1     ;1扇区
             mov al,1     ;只读一个扇区
             int 13h

;置CS,IP
mov ax,0
mov bx,7c00h
push ax
push bx
retf ;retf 相当于
     ;pop ip 
     ;pop cs 
     ;cs ip 的值不能通过普通的mov指令修改

;计算代码长度
ProgrameEnd:

Code ends

end Start

附上BIOS内存分布图
386工作在实模式下内存情况分布图

发布者

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注