本节内容:打印I/O源程序和时钟显示程序。
■打印I/O源程序:t16-6.asm。
■时钟显示程序:t16-7.asm。
16.4.1 打印I/O源程序
我们以打印I/O程序的源程序,作为BIOS中断处理程序设计的例子。每个打印机有三个寄存器:数据寄存器、状态寄存器和控制寄存器。三个寄存器的端口地址都是连续的,系统初始化期间,数据寄存器端口地址被依次保存到BIOS数据区,从40H段的偏移8H处开始。
尽管通过中断控制器8259A可实现中断方式打印输出,但BIOS提供的打印I/O程序却采用查询方式实现打印输出。查询时使用的超时参数在BIOS数据区中,从40H段的偏移78H开始。
BIOS提供的打印I/O程序作为17H中断处理程序,实现流程图16-8所示。
图16-8 打印I/O程序的流程
作为BIOS软中断处理程序,没有自己的堆栈,而是使用主程序的堆栈。
示例代码77:
;BIOS打印I/O程序的源程序
;程序名:biosio.asm
;---------------------------
assume cs:code
;常量说明
print_tim_out=78h ;超时参数存放单元开始偏移
printer_base=8 ;端口地址存放单元开始地址偏移
bios_data_seg=40h ;BIOS数据段的段值
;代码部分
code segment
print_io proc far
sti ;开中断
push ds
push dx
push si
push cx
push bx
mov bx,bios_data_seg
mov ds,bx ;置BIOS数据段段值
mov si,dx ;DX=0,0号打印机
mov bl,print_tim_out[si] ;取超时测试基本单位数
shl si,1
mov dx,printer_base[si] ;取数据寄存器端口地址
or dx,dx ;判断系统是否有对应接口
jz b1 ;无,转结束
or ah,ah ;0号功能?
jz b2
dec ah ;1号功能?
jz b8 ;是,转
dec ah ;2号功能?
jz b5
b1: pop bx
pop cx
pop si
pop dx
pop ds
iret
;0号处理功能
b2: push ax
out dx,al ;输出打印机数据
inc dx ;DX=状态寄存器端口地址
push bx
sub bh,bh
rcl bx,1 ;确定超时参数单位数
rcl bx,1
b3: sub cx,cx ;每一单位测试65536次
b3_1: in al,dx ;读状态寄存器
mov ah,al
test al,80h ;是否忙碌?
jnz b4 ;否转
loop b3_1 ;是,继续测
dec bx ;单位数完?
jnz b3 ;否,转
pop bx ;是
or ah,1 ;置超时标志位
and ah,0f9h ;去掉无定义位
jmp short b7 ;转,中断返回
b4: pop bx
mov al,0dh ;选通,即真正输出到打印机
inc dx
out dx,al
mov al,0ch
jmp $+2
out dx,al
pop ax
;2号功能处理
b5: push ax
b6: mov dx,printer_base[si]
inc dx ;DX=状态寄存器端口地址
mov ah,al
and ah,0f8h ;去掉无定义位和清超时标志位
b7: pop dx
mov al,dl
xor ah,48h ;第6位和第3位取反(符合出口约定)
jmp b1 ;转,中断返回
;1号功能处理
b8: push ax
inc dx
inc dx ;DX=控制寄存器端口地址
mov al,8
out dx,al ;输出初始化命令
mov ax,10000
b9: dec ax ;等待一段时间
jnz b9
mov al,0ch ;输出正常控制命令
out dx,al
jmp b6 ;转取状态字节和中断返回
print_io endp
code ends
end
上述17H中断处理程序直接操纵控制打印接口,没有再调用其他程序。
16.4.2 时钟显示程序
在系统加电初始化期间,把系统定时器初始化为每隔55毫秒发出一次中断请求。
CPU在响应中断请求后转入8H中断处理程序。BIOS提供的8H号中断处理程序中有一条中断指令“INT 1CH”,所以每秒调用约18.2次1CH号中断处理程序。实际上1CH中断没有做任何工作,只有一条返回指令。目的是为了给应用程序留下一个软接口,应用程序只要提供新的1CH号中断处理程序,就可以实现某些周期性的工作。
下面介绍的时钟显示程序就是利用这个软接口,实现时钟显示。
新的1CH号中断处理程序中安排一个计数器,记录调用它的次数,当计数器满18次后,就在屏幕的右上角显示当前的时间(时,分,秒),清计数器。约每秒显示一次当前时间。
当前时间的获取是调用1AH号中断处理程序的2号功能完成的,该功能在CH,CL和DH寄存器返回时,分,秒的BCD码。转换为对应的十进制数ASCII码后,调用显示I/O程序完成显示。
动手实验102:写一个利用1CH号中断软接口实现时钟显示的程序。
在理解下面示例程序的基础上,自己独立编写源程序。编译完成后,在debug调试器中单步跟踪调试,以验证程序的正确性。
算法分析:
首先保存原1CH号中断处理程序,然后设置新的1CH,完成工作后,再恢复。
示例代码78:
;程序名:t16-7.asm
;功能:利用1CH号中断软接口,实现时钟显示
;首先保存原1CH号中断处理程序,然后设置新的1CH,完成工作后,再恢复
;作为例子,这里的主程序实际上没有进行实质性的工作
;--------------------------------------------------
assume cs:code,ds:code
;中断处理程序常量定义
count_val=18 ;间隔“滴答”数
dpage=0 ;显示页号
row=0 ;显示时钟的行号
column=80-buff_len ;显示时钟的开始列号
color=07h ;显示时钟的属性值
;代码
code segment
;1CH号中断处理程序使用的变量
count dw count_val ;嘀嗒计数
hhhh db ?,?,':' ;时
mmmm db ?,?,':' ;分
ssss db ?,? ;秒
buff_len=$-offset hhhh ;buff_len为显示信息长度
cursor dw ? ;原光标位置
;=======================================================
;1CH号中断处理程序代码
new1ch:
cmp cs:count,0 ;是否已到显示时候?
jz next ;是,转
dec cs:count
iret ;中断返回
next:
mov cs:count,count_val ;重置间隔数初值
sti ;开中断
push ds
push es
push ax
push bx
push cx
push dx
push si
push bp
push cs
pop ds
push ds
pop es ;置代码段寄存器
call get_t ;取时间
mov bh,dpage
mov ah,3 ;取原光标位置
int 10h
mov cursor,dx ;保存原光标位置
mov bp,offset hhhh
mov bh,dpage
mov dh,row
mov dl,column
mov bl,color
mov cx,buff_len ;?
mov al,0
mov ah,13h ;显示时钟
int 10h
mov bh,dpage ;恢复原光标
mov dx,cursor
mov ah,2
int 10h
pop bp
pop si
pop dx
pop cx
pop bx
pop ax
pop es
pop ds
iret
;
;-----------------------------------------------
;子程序说明信息略
get_t proc
mov ah,2 ;取时间信息
int 1ah ;时钟管理中断
mov al,ch ;把时数转为可显示形式
call ttasc
xchg ah,al
mov word ptr hhhh,ax
mov al,cl ;把分数转为可显示形式
call ttasc
xchg ah,al
mov word ptr mmmm,ax ;保存
mov al,dh ;把秒数转为可显示形式
call ttasc
xchg ah,al
mov word ptr ssss,ax ;保存
ret
get_t endp
;------------------------------------------------
;子程序:ttasc
;功能:把两位压缩的BCD码转换为对应的ASCII码
;入口参数:AL=压缩BCD码
;出口参数:AH=高位BCD码所对应的ASCII码,AL=低位BCD码所对应的ASCII码
ttasc proc
mov ah,al
and al,0fh
shr ah,1
shr ah,1
shr ah,1
shr ah,1
add ax,3030h
ret
ttasc endp
;-----------------------------
;初始化部分代码变量
old1ch dd ? ;原中断向量保存单元
start:
push cs
pop ds
mov ax,351ch ;取1CH号中断向量
int 21h
mov word ptr old1ch,bx ;保存
mov word ptr old1ch+2,es
;
mov dx,offset new1ch
mov ax,251ch
int 21h
;......
;其他工作
mov ah,0 ;假设其他工作是等待按键
int 16h
;
lds dx,old1ch ;取保存的原1CH号中断向量
mov ax,251ch
int 21h ;恢复原1CH号中断向量
;
mov ax,4c00h
int 21h
code ends
end start
练习
1、基本输入输出系统BIOS主要含有哪些内容?是如何实现的?
2、简述应用程序实现输入输出的几种方式。
3、简述实现键盘输入的流程。
4、写一个程序采用十六进制数的形式显示所按键的扫描码及对应的ASCII码。连续两次按回车键时结束程序。
5、可以使用多少种方法实现在屏幕左上角显示AB两个字符?请比较这些方法。
6、使用两种不同的方法实现清屏。
7、写一个程序,采用直接写屏的方法在屏幕上循环显示26个大写字母。当按任一键后终止程序,通过调用BIOS键盘缓冲管理模块的1号功能判别是否有按键按下。
8、写一个程序在屏幕上循环显示26个大写字母,每行显示10个,逐行变化显示颜色,当按下ALT+F1键时终止程序。
9、可以有多少种方法实现“把屏幕上显示的大写字母全部变换成对应的小写字母”。
10、写一个程序统计当前屏幕上正在显示的字母符个数。
11、写一个程序判别屏幕上是否有显示字符串'AB'。在屏幕的最底行显示提示信息,按任意键终止程序。
12、请写一个显示打印接口当前状态的程序。
13、请写一个将TEST.TXT文件打印输出的程序。
14、修改9H号键盘中断程序,使所按的大写字母全部变换为对应的小写字母。
15、修改16H号键盘I/O程序,使所按的大写字母全部变换为对应的小写字母。
16、修改10H号显示I/O程序,使所按的大写字母全部变换为对应的小写字母。
17、写一个程序在屏幕中央显示系统当前时间,同时按下左右SHIFT键时,清屏并结束程序。
18、如何不使用软中断指令INT调用16H中断处理程序,请给出实例。
本文摘自编程达人系列教材《X86汇编语言基础教程》。