从入门到精通汇编语言 第六章(中断及外部设备操作)
参考教程:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili
一、移位指令
1、8个移位指令
(1)逻辑左移指令SHL:SHL OPR, CNT。
①OPR为操作数,CNT为左移位数,该指令将OPR视作二进制无符号数,向左移位相应的位数,低位补0,最后一个被移出的高位写入CF中。
②当CNT大于1时,必须将其存入寄存器CL中,以寄存器名字CL的形式给出。
(2)逻辑右移指令SHR:SHR OPR, CNT。
①OPR为操作数,CNT为右移位数,该指令将OPR视作二进制无符号数,向右移位相应的位数,高位补0,最后一个被移出的低位写入CF中。
②当CNT大于1时,必须将其存入寄存器CL中,以寄存器名字CL的形式给出。
(3)循环左移指令ROL:ROL OPR, CNT。
①OPR为操作数,CNT为左移位数,该指令将OPR视作二进制数,向左移位相应的位数,被移出的高位会从低位移入,最后一个被移出的高位写入CF中。
②当CNT大于1时,必须将其存入寄存器CL中,以寄存器名字CL的形式给出。
(4)循环右移指令ROR:ROR OPR, CNT。
①OPR为操作数,CNT为右移位数,该指令将OPR视作二进制数,向右移位相应的位数,被移出的低位会从高位移入,最后一个被移出的低位写入CF中。
②当CNT大于1时,必须将其存入寄存器CL中,以寄存器名字CL的形式给出。
(5)算数左移指令SAL:SAL OPR, CNT。
①OPR为操作数,CNT为左移位数,该指令将OPR视作二进制有符号数,向左移位相应的位数,低位补0,最后一个被移出的高位写入CF中。
②当CNT大于1时,必须将其存入寄存器CL中,以寄存器名字CL的形式给出。
(6)算数右移指令SAR:SAR OPR, CNT。
①OPR为操作数,CNT为右移位数,该指令将OPR视作二进制有符号数,向右移位相应的位数,每移一位时高位补0或1取决于次高位是0或1(与次高位相同),最后一个被移出的低位写入CF中。
②当CNT大于1时,必须将其存入寄存器CL中,以寄存器名字CL的形式给出。
(7)带进位循环左移RCL:RCL OPR, CNT。
①OPR为操作数,CNT为左移位数,该指令将OPR视作二进制数,向左移位相应的位数,每移一位时,原高位写入CF,原CF的内容从低位移入。
②当CNT大于1时,必须将其存入寄存器CL中,以寄存器名字CL的形式给出。
(8)带进位循环右移RCR:RCR OPR, CNT。
①OPR为操作数,CNT为右移位数,该指令将OPR视作二进制数,向右移位相应的位数,每移一位时,原低位写入CF,原CF的内容从高位移入。
②当CNT大于1时,必须将其存入寄存器CL中,以寄存器名字CL的形式给出。
2、移位指令使用示例
(1)以逻辑移位指令进行示例:将X逻辑左移一位,相当于执行X = X * 2;将X逻辑右移一位,相当于执行X = X / 2。
(2)汇编程序:
assume cs:code
code segment
main: mov al, 00000001b ;执行后(al)=00000001b=1shl al, 1 ;执行后(al)=00000010b=2shl al, 1 ;执行后(al)=00000100b=4shl al, 1 ;执行后(al)=00001000b=8mov cl, 3shl al, cl ;执行后(al)=01000000b=64mov cl, 2shr al, cl ;执行后(al)=00010000b=16mov ax, 4c00hint 21hcode ends
end main
二、操作显存数据
1、显示的原理
(1)8086的内存空间中有这么一块显存地址空间,屏幕上的显示内容和显存地址空间中的数据一一对应。
(2)通过往显示缓冲区中写入数据,可以实现在屏幕上显示特定属性字符的效果。
2、显示缓冲区的结构
(1)显示缓冲区总共25行80列(单位为字),每个字由两个字节组成,其中低位字节存放要显示符号的ASCII码,高位字节存放要显示字符的属性。
(2)字符的显示属性由8位组成,其中0-2位为前景的RGB参数(三色参数均仅有0或1可选),3位决定是否高亮,4-6位为背景的RGB参数(三色参数均仅有0或1可选),7为决定是否闪烁。
3、举例
(1)目的:编写汇编程序,在屏幕的中间,属性为白底蓝字,显示‘Welcome to masm!’。
(2)汇编程序:
assume cs:code, ds:data
data segmentdb ‘Welcome to masm!’
data endscode segmentmain: mov ax, data ;获取数据段地址mov ds, ax ;将数据段地址送入DS中mov ax, 0b800h ;获取显示缓冲区首地址mov es, ax ;将显示缓冲区首地址送入ES中mov si, 0mov di, 160*12+80-16mov cx, 16w: mov al, [si]mov es:[di], al ;将字符ASCII码载入缓冲区inc di ;操作下一个字节mov al, 71hmov es:[di], al ;将字符属性载入缓冲区inc si ;指向数据区字符串的下一个字符inc di ;操作下一个字节loop wmov ax, 4c00hint 21hcode ends
end main
三、描述内存单元的标号
1、数据标号
(1)代码段中的标号可以用来标记指令、段的起始地址,也可以用来标记数据所在的位置。如下汇编程序,其作用是将a标号处的8个字节数据累加,结果存储到b标号处的字中。
assume cs:code
code segmenta: db 1, 2, 3, 4, 5, 6, 7, 8b: dw 0start: mov si,offset a ;获取标号a处“数据堆”的首地址mov bx,offset b ;获取标号b处“数据堆”的首地址mov cx,8s: mov al,cs:[si]mov ah,0add cs:[bx],axinc siloop smov ax,4c00hint 21h
code ends
end start
(2)数据标号可以把冒号去掉,此时数据标号不同于仅仅表示地址的地址标号,它同时描述内存地址和单元长度。如下汇编程序,其作用是将a标号处的8个字节数据累加,结果存储到b标号处的字中。
assume cs:code
code segmenta db 1, 2, 3, 4, 5, 6, 7, 8 ;标号a以后的内存单元最小单位都是字节b dw 0 ;标号b以后的内存单元最小单位都是字start: mov si,0 mov cx,8s: mov al,a[si] ;(al) = (cs * 16 + a + si)mov ah,0add b,ax ;(cs * 16 + b) = (ax)inc siloop smov ax,4c00hint 21h
code ends
end start
2、数据的直接定址表
(1)数据标号除了可用于标识代码段中的数据以外,还可以用于标识数据段中的数据。如下汇编程序,其作用是将a标号处的8个字节数据累加,结果存储到b标号处的字中。
assume cs:code, ds:data
data segmenta db 1, 2, 3, 4, 5, 6, 7, 8 ;标号a以后的内存单元最小单位都是字节b dw 0 ;标号b以后的内存单元最小单位都是字
data ends
code segmentstart: mov ax, datamov ds, axmov si,0 mov cx,8s: mov al,a[si] ;(al) = (ds * 16 + a + si)mov ah,0add b,ax ;(ds * 16 + b) = (ax)inc siloop smov ax,4c00hint 21h
code ends
end start
(2)标号可以当作数据定义,如下所示。
assume cs:code, ds:data
data segmenta db 1, 2, 3, 4, 5, 6, 7, 8b dw 0c dw offset a, seg a, offset b, seg b
data ends
code segmentstart: mov ax, datamov ds, axmov si,0 mov cx,8s: mov al,a[si] ;(al) = (ds * 16 + a + si)mov ah,0add b,ax ;(ds * 16 + b) = (ax)inc siloop smov ax,4c00hint 21h
code ends
end start
(3)鉴于标号可以当作数据定义,不妨尝试给若干组数据用标号标识,把这些标号全部搁一起,当作一组数据定义,这样就能得到一个数据直接定址表,换句话说,利用数据直接定址表可在两个数据集合之间建立一种映射关系,用查表的方法根据给出的数据得到其在另一集合中的对应数据。
(4)举例:编写程序,计算sin(x),x∈{0°,30°,60°,90°,120°,150°,180°},并在屏幕中间显示计算结果。
①解决方案:空间换时间,将所要计算的sin(x) 的结果都存储到一张表中,然后用角度值来查表,找到对应的sin(x)的值,并显示在屏幕上。
②汇编程序:
assume cs:code
code segmentstart: mov al,60 ;用ax向子程序传递角度值call showsinmov ax,4c00hint 21hshowsin: jmp short show ;转移至子函数下一条代码处table dw ag0, ag30, ag60, ag90, ag120, ag150, ag180ag0 db '0' ,0 ;sin(0)对应的字符串'0'ag30 db '0.5' ,0 ;sin(30)对应的字符串'0.5'ag60 db '0.866', 0 ;sin(60)对应的字符串'0.866'ag90 db '1' ,0 ;sin(90)对应的字符串'1'ag120 db '0.866' ,0 ;sin(120)对应的字符串'0.866'ag150 db '0.5', 0 ;sin(150)对应的字符串'0.5'ag180 db '0', 0 ;sin(180)对应的字符串'0'show: push bxpush espush simov bx, 0b800hmov es, bxmov ah, 0mov bl, 30div bl ;用角度值/30作为相对于table的偏移量mov bl, al mov bh,0add bx, bx ;注意table与其它标号描述的内存单元大小mov bx, table[bx] ;取得对应的字符串的偏移地址,放在bx中mov si, 160*12+40*2shows: mov ah, cs:[bx]cmp ah, 0je showretmov es:[si],ahinc bxadd si,2jmp showsshowret: pop sipop espop bxret
code ends
end start
3、代码的直接定址表
(1)除了数据有直接定址表以外,代码也可以有直接定址表,其实现思路是将若干个功能写成相应的若干个子程序,将这些功能子程序的入口地址存储在一个表中,它们在表中的位置和功能号相对应,对应关系为“功能号 * 2 = 对应的功能子程序在地址表中的偏移”。
(2)举例:
①目标:实现一个子程序setscreen,为显示输出提供如下功能。
[1]清屏。
[2]设置前景色。
[3]设置背景色。
[4]向上滚动一行
②子程序入口参数说明:
[1]用AH寄存器传递功能号,0表示清屏,1表示设置前景色,2表示设置背景色,3表示向上滚动一行。
[2]对2、3号功能,用AL传送颜色值,(al)∈{0, 1, 2, 3, 4, 5, 6, 7 }。
③各个功能的子程序实现:
[1]清屏:将显存中当前屏幕中的字符设为空格符。
sub1:push bxpush cxpush esmov bx, 0b800hmov es, bxmov bx, 0mov cx, 2000
sub1s:mov byte ptr es:[bx], ' 'add bx, 2loop sub1spop espop cxpop bxret ;sub1结束
[2]设置前景色:设置显存中奇地址的属性字节的第0、1、2位。
sub2:push bxpush cxpush esmov bx, 0b800hmov es, bxmov bx, 1mov cx, 2000
sub2s:and byte ptr es:[bx], 11111000bor es:[bx], aladd bx, 2loop sub2spop espop cxpop bxret ;sub2结束
[3]设置背景色:设置显存中奇地址的属性字节的第4、5、6位。
sub3:push bxpush cxpush esmov cl, 4shl al, clmov bx, 0b800hmov es, bxmov bx, 1mov cx, 2000
sub3s:and byte ptr es:[bx],10001111bor es:[bx], aladd bx, 2loop sub3spop espop cxpop bxret ; sub3结束
[4]向上滚动一行:依次将第n+1行的内容复制到第n行处,并清空最后一行。
sub4:push cxpush sipush dipush espush dsmov si, 0b800hmov es, simov ds, simov si,160 ;ds:si指向第n+1行mov di, 0 ;es:di指向第n行cldmov cx, 24 ;共复制24行sub4s:push cxmov cx, 160rep movsb ;复制1行pop cxloop sub4smov cx,80mov si,0
sub4s1:mov byte ptr es:[160*24+si], ' ' ;清空最后一行add si,2loop sub4s1pop dspop espop dipop sipop cxret ;sub4结束
④主程序与setscreen子程序:
assume cs:code
code segment
start:mov ah, 2mov al, 5call setscreenmov ax, 4c00hint 21hsetscreen: ;要在其中再加入新功能,只需要在地址表中加入它的入口地址即可jmp short settable dw sub1,sub2,sub3,sub4 ;地址表
set:push bxcmp ah,3ja sretmov bl,ahmov bh,0add bx,bxcall word ptr table[bx] ;根据bx中的功能号索引相应的标号,执行其子程序
sret:pop bxret;4个功能的子程序放在此处
code ends
end start
四、中断及其处理
1、中断的概念与分类
(1)中断是指CPU不再接着(刚执行完的指令)向下执行,而是转去处理中断信息。
(2)中断的分类:
①内中断:由CPU内部发生的事件而引起的中断。
②外中断:由外部设备发生的事件引起的中断。
2、8086的内中断
(1)CPU内部产生的中断信息:
①除法错误,比如执行DIV指令时产生除法溢出(除0错误)。
②单步执行中断。
③INTO命令。
④INT命令。
(2)8086的中断类型码:
①除法错误:0。
②单步执行中断:1。
③INTO命令:4。
④INT <立即数n>命令:立即数n。
3、中断处理程序
(1)CPU处理中断信息,本质上就是执行中断处理程序。
(2)中断向量表:由中断类型码可查表得到中断处理程序的入口地址(低字节存放IP-偏移地址,高字节存放CS-代码段地址),从而定位中断处理程序。((IP) = (N*4),(CS) = (N*4+2),N为中断类型码)
(3)举例:触发系统的0号中断,CPU会根据中断类型码在中断向量表中找到中断处理程序的入口地址,并根据入口地址设置CS寄存器与IP寄存器,将转至中断服务程序执行。
五、编制中断处理程序
1、中断处理程序及其结构
(1)CPU随时都可能检测到中断信息,所以中断处理程序必须常驻内存(一直存储在内存某段空间之中),中断处理程序的入口地址,也即中断向量,必须存储在对应的中断向量表表项中(0000H:0000H-0000H:03FFH)。
(2)触发并进入中断处理程序的过程:
①取得中断类型码N。
②pushf —— 标志寄存器内容入栈(保存标志寄存器)。
③TF = 0,IF = 0 —— 防止非预期的中断嵌套触发。
④push CS —— 保存原程序断点。
⑤push IP —— 保存原程序断点。
⑥(IP) = (N*4)、(CS) = (N*4+2) —— 转移至N号中断的中断服务程序。
2、编制中断处理程序——以除法错误中断为例
(1)预期效果:编写一个0号中断处理程序do0,它的功能是在屏幕中间显示“overflow!”后,返回到操作系统。
(2)准备工作:
①do0子程序应该存放在内存的确定位置,并且要重新找个地方,不破坏系统,可利用中断向量表中的空闲单元来存放我们的程序。经过估计,do0的长度不可能超过256个字节,就选用从0000:0200至0000:02FF的256个字节的空间。
②0号中断处理程序要有新的入口地址(需说明,实际应用中不要随便自己改写中断处理程序)。
(3)程序框架梳理:
①编写可以显示“overflow!”的中断处理程序do0。
②将do0送入内存0000H:0200H处(安装程序)。
③将do0中断处理程序的入口地址0000H:0200H存储在中断向量表0号表项中。
(4)汇编程序:
assume cs:code
code segment
start:;安装程序do0mov ax, csmov ds, ax ;do0的段地址送入DS中mov si, offset do0 ;获取do0的偏移地址mov ax, 0mov es, axmov di, 200h ;ES:DI指向0000H:0200Hmov cx, offset do0end - offset do0 ;获取do0程序所占用字节数cldrep movsb ;将do0下的内容送入内存0000H:0200H处;设置中断向量表mov ax, 0mov es, axmov word ptr es:[0*4], 200hmov word ptr es:[0*4+2], 0mov ax,4c00hint 21h
do0:jmp short do0startdb ‘overflow!’
do0start:mov ax, csmov ds, axmov si, 202hmov ax, 0b800hmov es, axmov di, 12*160+36*2mov cx, 9
s: mov al, [si]mov es:[di], alinc siadd di, 2loop smov ax, 4c00hint 21h
do0end: nop
code ends
end start
(5)测试:运行上面的程序,改变中断向量,然后执行DIV指令,除数为0,触发除0错误,观察屏幕现象。
六、单步中断
1、Debug的T命令回顾
(1)Debug利用了CPU提供的单步中断的功能,使用T命令时,Debug会将TF标志设为1,使CPU工作在单步中断方式下。
(2)每使用一次T命令,Debug就会执行一条指令,并显示寄存器中的内容和下一条需要执行的指令(CS:IP指向该条指令)。
2、单步中断处理过程
(1)两个和中断相关的寄存器标志位:
①TF-陷阱标志(Trap flag):当TF=1时,每条指令执行完后产生陷阱,由系统控制计算机;当TF=0时,CPU正常工作,不产生陷阱。(用于调试时的单步方式操作)
②IF-中断标志(Interrupt flag):当IF=1时,允许CPU响应可屏蔽中断请求;当IF=0时,关闭中断。
(2)CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断(中断类型码为1),引发中断过程,执行中断处理程序。
(3)进入中断处理程序时需要将TF置为0,这是因为中断处理程序也由一条条指令组成的,如果在执行中断处理程序之前TF=1,则CPU在执行完中断处理程序的第一条指令后又要产生单步中断,转去执行单步中断的中断处理程序的第一条指令,以此往复,将陷入一个永远不能结束的循环,CPU永远执行单步中断处理程序的第一条指令,所以在进入中断处理程序之前,需要设置TF=0。
(4)一般情况下,CPU在执行完当前指令后,如果检测到中断信息就响应中断,引发中断过程。不过在有些情况下,CPU 在执行完当前指令后,即便是发生中断,也不会响应,如在执行完向SS寄存器传送数据的指令后,即便是发生中断,CPU也不会响应,这是因为SS:SP联合指向栈顶,而对它们的设置应该连续完成(实际上如果不连续设置SS和SP,编译阶段也不会报错,但编程时应养成良好的习惯),以此保证对栈的正确操作。
七、由INT指令引发的中断
1、INT指令介绍
(1)格式:INT <立即数n>。(n为中断类型码)
(2)INT指令可无条件引发任何中断过程,CPU执行“int n”指令,相当于引发一个n号中断的中断过程,执行过程如下:
①取得中断类型码N。
②pushf —— 标志寄存器内容入栈(保存标志寄存器)。
③TF = 0,IF = 0 —— 防止非预期的中断嵌套触发。
④push CS —— 保存原程序断点。
⑤push IP —— 保存原程序断点。
⑥(IP) = (N*4)、(CS) = (N*4+2) —— 转移至N号中断的中断服务程序。
(3)一般情况下,系统将一些具有一定功能的子程序,以中断处理程序的方式提供给应用程序调用。
2、编写供应用程序调用的中断例程
(1)编程时,可以用INT指令调用子程序,此子程序即中断处理程序,简称为中断例程(与一般的子程序一样,需注意保存现场和恢复现场)。可以自定义中断例程,实现特定功能。
(2)举例:写7ch号中断的中断例程,完成特定任务。
①目标:求一个word型数据的平方,用AX进行参数传递,DX、AX中分别存放结果的高16位、低16位。
②任务分解:
[1]编程实现求平方功能的程序。
[2]安装程序,将其安装在0000H:0200H处。
[3]设置中断向量表,将程序的入口地址保存在7ch表项中,使其成为中断7ch的中断例程。
③知识补充:IRET指令常用于中断处理函数结尾处,它相当于指令“pop ip”、“pop cs”、“popf”(标志寄存器内容出栈)。
④安装中断例程的汇编程序:
assume cs:code
code segment
start: mov ax, csmov ds, axmov si, offset sqrmov ax ,0mov es, axmov di, 200hmov cx, offset sqrend - offset sqrcldrep movsbmov ax, 0mov es, axmov word ptr es:[7ch*4], 200hmov word ptr es:[7ch*4+2], 0mov ax,4c00hint 21hsqr: mul axiret
sqrend: nop
code ends
end start
⑤测试使用的汇编程序:
assume cs:code
code segment
start: mov ax,3456int 7ch ;引发7ch号中断,计算(ax)^2add ax,axadc dx, dxmov ax,4c00hint 21h
code ends
end start
八、BIOS和DOS中断处理
1、BIOS——基本输入输出系统
(1)BIOS是在系统板的ROM中存放着的一套程序,容量为8KB,从FE000H开始。
(2)BIOS中的主要内容:
①硬件系统的检测和初始化程序。
②外部中断和内部中断的中断例程。
③用于对硬件设备进行I/O操作的中断例程。
④其它和硬件系统相关的中断例程。
(3)使用BIOS功能调用,程序员不用了解硬件操作细节,直接使用指令设置参数,并中断调用BIOS例程,即可完成相关工作。
(4)BIOS具体有哪些功能可查找BIOS中断手册,里面有详细的介绍,这里不再赘述。
2、DOS中断
(1)通过执行指令“int 21”,可引发DOS中断类,和硬件设备相关的DOS中断例程中,一般都调用BIOS的中断例程。
(2)BIOS和DOS在所提供的中断例程中包含了许多子程序,这些子程序实现了程序员在编程的时常用到的功能。
3、BIOS和DOS中断例程的安装过程
(1)CPU一上电,初始化(CS)=0FFFFH,(IP)=0,自动从FFFFH:0000H单元开始执行程序。FFFFH:0000H处有一条转跳指令,CPU执行该指令后,转去执行BIOS中的硬件系统检测和初始化程序。
(2)初始化程序将建立BIOS 所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表中。
(3)硬件系统检测和初始化完成后,调用“int 19h”进行操作系统的引导,从此将计算机交由操作系统控制。
(4)DOS启动后,除完成其它工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。
九、端口的读写
1、IN指令与OUT指令
(1)CPU可以直接读写3个地方的数据——CPU内部的寄存器、内存单元、端口,从CPU角度,可以将各寄存器当作端口并统一编址,CPU用统一的方法与各种设备通信。
(2)读写端口需要用专门的指令IN和OUT,IN指令用于CPU从端口读取数据,OUT用于CPU往端口写入数据。
(3)“IN <寄存器> <端口地址>”执行的操作是将端口地址(可以其它形式给出,如存储在寄存器中)中的数据读入CPU相应的寄存器中,执行该指令时总线有如下相关操作:
①CPU通过地址线将端口地址信息发出。
②CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知要从中读取数据。
③端口所在的芯片将端口中的数据通过数据总线送入CPU。
(4)“OUT <端口地址> <寄存器>”执行的操作是将CPU相应的寄存器中的数据写入端口地址(可以其它形式给出,如存储在寄存器中)对应的空间中,执行该指令时总线有如下相关操作:
①CPU通过地址线将端口地址信息发出。
②CPU通过控制线发出端口写命令,选中端口所在的芯片,并通知要往里面写入数据。
③CPU通过数据总线将数据送入端口所在的芯片的端口中。
2、8086的I/O端口分配
3、用端口访问外设举例
(1)61h端口地址的设备控制寄存器功能如下所示:
(2)汇编程序:
assume cs:codeseg
codeseg segment
start: mov al, 08h ;设置声音的频率out 42h, alout 42h, alin al, 61h ;读设备控制器端口原值mov ah, al ;保存原值or al, 3 ;打开扬声器和定时器out 61h, al ;接通扬声器,发声mov cx, 60000 ;延时
delay: noploop delaymov al, ah out 61h, al ;恢复端口原值mov ax, 4c00hint 21h
codeseg ends
end start
十、操作CMOS RAM芯片
1、CMOS RAM芯片介绍
(1)包含一个实时钟和一个有128个存储单元的RAM存储器。
(2)128个字节的RAM中存储:内部实时钟、系统配置信息、相关的程序(用于开机时配置系统信息)。
(3)CMOS RAM 芯片靠电池供电,关机后其内部的实时钟仍可正常工作,RAM中的信息不丢失。
(4)该芯片内部有两个端口,端口地址为70h和71h,CPU通过这两个端口可以读写CMOS RAM。
①70h地址端口存放要访问的CMOS RAM单元的地址。
②71h数据端口存放从选定的单元中读取的数据,或要写入到其中的数据。
2、举例——提取CMOS RAM中存储的月份信息
(1)背景知识:CMOS RAM中以BCD码的形式存储时间信息,其中月份信息存储在8号单元中,具体内容分布如下所示。
(2)任务分解:
①从CMOS RAM的8号单元读出当前月份的BCD码。
②将用BCD码表示的月份以十进制的形式显示到屏幕上。
(3)汇编程序:
assume cs:code
code segment
start: mov al, 8out 70h, al ;存放要访问的CMOS RAM单元的地址(8号单元)in al, 71h ;将其中的月份信息读入ALmov ah, almov cl, 4shr ah, cland al, 00001111badd ah, 30hadd al, 30hmov bx, 0b800hmov es, bxmov byte ptr es:[160*12+40*2], ahmov byte ptr es:[160*12+40*2+2], almov ax, 4c00hint 21h
code ends
end start
十一、外设连接与中断
1、由外部设备发生的事件引起的中断(外中断)
(1)可屏蔽中断与不可屏蔽中断:
①可屏蔽中断是CPU 可以不响应的外中断,CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置,当CPU检测到可屏蔽中断信息时,如果IF=1,则CPU在执行完当前指令后响应中断,引发中断过程,如果IF=0,则不响应可屏蔽中断。
②不可屏蔽中断是CPU必须响应的外中断,当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后立即响应,引发中断过程。对于8086CPU,不可屏蔽中断的中断类型码固定为2。
(2)几乎所有由外设引发的外中断都是可屏蔽中断,比如键盘输入、打印机请求;不可屏蔽中断在系统中有必须处理的紧急情况发生时用来通知CPU的中断信息。
(3)CPU在执行指令过程中,可以检测到外设发送过来的中断信息,引发中断过程,处理外设的输入。
2、中断的处理过程
(1)可屏蔽中断所引发的中断过程:
①取中断类型码n(可屏蔽中断信息来自于CPU外部,中断类型码通过数据总线送入CPU)。
②pushf —— 标志寄存器内容入栈(保存标志寄存器)。
③TF = 0,IF = 0 —— 防止非预期的中断嵌套触发,并禁止其它可屏蔽中断(如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF置1)。
④push CS —— 保存原程序断点。
⑤push IP —— 保存原程序断点。
⑥(IP) = (N*4)、(CS) = (N*4+2) —— 转移至N号中断的中断服务程序。
(2)不可屏蔽中断的中断过程:
①中断值固定为2,不必取中断码。
②pushf —— 标志寄存器内容入栈(保存标志寄存器)。
③TF = 0,IF = 0 —— 防止非预期的中断嵌套触发,并禁止其它可屏蔽中断(如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF置1)。
④push CS —— 保存原程序断点。
⑤push IP —— 保存原程序断点。
⑥(IP)=(8)、(CS)=(0AH)。
3、STI和CLI指令
(1)STI指令无操作数,它用于设置IF=1。
(2)CLI指令无操作数,它用于设置IF=0。
十二、PC机键盘的处理过程
1、第一步——键盘输入
(1)键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描。
(2)按下一个键时的操作:
①开关接通,该芯片产生一个扫描码,扫描码说明了按下的键在键盘上的位置。
②扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60H。
(3)松开按下的键时的操作:
①产生一个扫描码,扫描码说明了松开的键在键盘上的位置。
②松开按键时产生的扫描码也被送入60H端口中。
(4)扫描码——长度为一个字节的编码:
①按下一个键时产生的扫描码——通码,通码的第7位为0。
②松开一个键时产生的扫描码——断码,断码的第7位为1。
③通码 + 80H = 断码。
2、第二步——引发9号中断
(1)键盘的输入到达60H端口时,相关的芯片就会向CPU发出中断类型码为9的可屏蔽中断信息,CPU检测到该中断信息后,如果IF=1,则响应中断,引发中断过程,转去执行int 9中断例程。
(2)BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9中断例程所接收的键盘输入的内存区,可以存储15 个键盘输入,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码。
(3)若输入了控制键或切换键,将会修改键盘状态字节,其地址为0040H:0017H,具体定义如下。
3、第三步——执行int 9中断例程
(1)读出60H端口中的扫描码。
(2)根据扫描码分情况对待:
①如果是字符键的扫描码,将该扫描码和它所对应的字符码(即ASCII码)送入内存中的BIOS键盘缓冲区。
②如果是控制键(比如Ctrl)和切换键(比如CapsLock)的扫描码,则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元。
(3)对键盘系统进行相关的控制,如向相关芯片发出应答信息。
4、按照开发需求定制键盘输入处理举例
(1)需求分解:
①在屏幕中间依次显示字母'a'~'z',并可以让人眼看清。
②在显示的过程中,按下Esc键后,改变显示的颜色。
(2)策略说明:
①尽可能忽略硬件处理细节,充分利用BIOS提供的int 9中断例程对这些硬件细节进行处理,由此可以改写自编int 9中断,自编的中断处理程序需调用原来的int 9中断例程,并且要将中断向量表中的int 9中断例程的入口地址改为自编的中断处理程序的入口地址,在新中断处理程序中调用原来的int 9中断例程,还需要是原来的int 9中断例程的地址,这样,按下按键引发int 9中断就能执行定制的中断例程,并且不需要程序员考虑硬件处理细节。
②这个开发需求可能并不是全生命周期都需要的,所以,相关功能处理完以后需要将中断向量表还原为原来的内容,后续按下按键时CPU还是调用原来的int 9中断。
(3)汇编程序分步实现:
①依次显示'a'~'z',并可以让人眼看清,这就需要在字母切换的过程中加塞一堆“无用指令”。
assume cs:code
stack segmentdb 128 dup (0)
stack ends
code segment
start: mov ax, stackmov ss, axmov sp, 128mov ax, 0b800hmov es, axmov ah, 'a'
s: mov es:[160*12+40*2], ah ;显示字符call delay ;调用延时程序inc ahcmp ah, 'z'jna smov ax,4c00hint 21hdelay: push ax ;延时程序没有什么实质操作push dx ;纯浪费CPU算力mov dx, 10hmov ax, 0
s1: sub ax, 1sbb dx ,0cmp ax, 0jne s1cmp dx, 0jne s1pop dxpop axret
code ends
end start
②实现按下Esc键后改变显示的颜色。
assume cs:code
stack segmentdb 128 dup (0)
stack ends
data segmentdw 0,0
data ends
code segment
start: mov ax, stackmov ss, axmov sp, 128mov ax, datamov ds, ax;更改中断例程入口地址mov ax, 0mov es, axpush es:[9*4] ;保存旧中断例程入口pop ds:[0]push es:[9*4+2]pop ds:[2]mov word ptr es:[9*4], offset int9 ;设置新中断例程入口mov es:[9*4+2], cs;显示字母mov ax, 0b800hmov es, axmov ah, 'a'
s: mov es:[160*12+40*2], ah ;显示字符call delay ;调用延时函数inc ahcmp ah, 'z'jna s;恢复原来中断例程的入口地址mov ax, 0mov es, axpush ds:[0]pop es:[9*4]push ds:[2]pop es:[9*4+2]mov ax,4c00hint 21h;定义延时程序
delay: push axpush dxmov dx, 10hmov ax, 0
s1: sub ax, 1sbb dx ,0cmp ax, 0jne s1cmp dx, 0jne s1pop dxpop axret;定义中断例程
int9: push axpush bxpush esin al, 60h ;从60h端口读出键盘的输入pushfpushfpop bxand bh, 11111100bpush bxpopfcall dword ptr ds:[0] ;调用原int 9指令功能cmp al, 1 ;判断是否为Esc的扫描码,是则改变显示的颜色jne int9retmov ax, 0b800hmov es, axinc byte ptr es:[160*12+40*2+1]
int9ret:pop espop bxpop axiret
code ends
end start