当前位置: 首页 > news >正文

从入门到精通汇编语言 第六章(中断及外部设备操作)

参考教程:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_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

相关文章:

  • ONLYOFFICE深度解锁系列.3-OnlyOffice集成第三方软件原理揭秘:如何提高文件打开速度
  • 器件(七)—MOS管选型及驱动电路设计
  • 动态规划入门:背包问题求具体方案(以0-1背包问题为例)
  • 免费图片软件,可矫正倾斜、调整去底效果
  • 【二叉树专题】一道深入浅出的 DFS 题:求二叉树的直径(含通俗易懂讲解)
  • 精准分割 - 深入解析 Kubernetes 中的 NVIDIA Multi-Instance GPU (MIG)
  • JavaScript 笔记 --- part 4 --- Web API (part 2)
  • 自定义请求头导致跨域的解决办法
  • AI日报 - 2025年04月20日
  • 每天学一个 Linux 命令(26):less
  • 从入门到精通汇编语言 第五章(流程转移与子程序)
  • 源道-做股票应该从传统的技术图形转向行业规律思考
  • C++(23)—模板初阶
  • java面试篇(常见的集合底层原理)
  • AI大模型之模型幻觉
  • Redis——通信协议
  • 【大模型】单选数据集制作举例
  • Java技术栈 —— 网络带宽受限,图片预览加速
  • 5.4.1 Password控件的Password属性绑定问题
  • 06 GE Modifier
  • 新闻1+1丨居民水电气计量收费乱象,如何治?
  • 一周观展|上海,一系列特展大展渐次呈现
  • 五一出境游火爆:境外包车订单增长25%,日本酒店价格贵了好几倍
  • 8个月女婴被指受虐后体重仅6斤?潮州警方:未发现虐待,父母有抚养意愿
  • “特朗普的欧洲耳语者”:梅洛尼白宫之行真能打破美欧关税僵局?
  • 姜仁华任中国水稻研究所所长,胡培松院士卸任