接口技术复习整理

引言

本课程主要用于计算机控制

计算机控制的实质是不同信号的不同处理过程,一般经过“数据采集过程”和“过程控制”2个阶段

计算机在工业生产过程的闭环控制

总线分类

地址总线AB:用来传送CPU输出的地址信号,确定被访问的存储单元、I/O端口。地址线的根数决定了CPU的寻址范围

CPU的寻址范围 = \(2^n\), n:地址线根数

数据总线DB:在CPU与存储器、I/O接口之间数据传送的公共通路。数据总线的条数决定CPU一次最多可以传送的数据宽度

控制总线CB:用来传送各种控制信号

CPU

计算机的运算中心和控制中心,提供运算、判断能力

构成:运算器ALU、控制器CU、寄存器组

I/O接口

输入/输出接口,简写为I/O接口,是CPU与外部设备间的桥梁,外设通过I/O接口与主机连接,与CPU实现信息传送

外设通过I/O接口与总线连接的原因

CPU、内存能直接连接到总线,外设需要用I/O接口

一般的输入/输出设备都是机械的或机电相结合的产物,它们相对于高速的中央处理器来说,速度要得多

此外,不同外设的信号形式、数据格式也各不相同

因此,外部设备不能与CPU直接相连,需要通过相应的电路来完成它们之间的速度匹配、信号转换,并完成某些控制功能

微机

微机基本概念

微处理器即CPU,是微型机的主要核心部件,微机和其它大、中、小型计算机的根本区别:微机的CPU由运算器和控制器集成而成

微型计算机由微处理器、接口、I/O设备通过总线连接而成

微型计算机系统由硬件和软件构成,硬件由微处理器、接口、I/O设备通过总线连接而成;软件由系统软件和应用软件构成。硬件是基础,软件是灵魂,计算机的功能只有在硬件基础上通过软件才能发挥

微机工作过程

①微机上运行高级语言程序,先须经过编译、链接生成机器语言可执行程序才能直接运行

②机器语言程序是按照算法运算顺序由许多机器指令按一定顺序组成的程序,即程序是由多条有逻辑关系的指令组成

③数据和程序均以二进制代码的形式不加区别地存放在存储器中,存放位置由二进制地址指定

④由控制器控制整个机器语言程序和数据的存取以及程序的执行。而指令执行由运算器完成


为实现自动连续地执行程序,控制器设置一程序计数器PC,它可根据指令的长度自动增量(总是指向下一条指令)

只要给出程序中第一条指令的地址,控制器就可依据存储程序中的指令顺序周而复始地取指令、译码、执行,直到完成全部指令操作为止,即控制器通过指令流的串行驱动实现程序控制

微机分类

按照组装形式和系统规模,可以把微型计算机划分为单片机、单板机和个人计算机

单片机:将CPU、部分存储器、部分I/O接口集成在一个芯片

单板机:将CPU、存储器、I/O接口及部分I/O设备安装在一个印刷线路板

个人计算机PC:在主板上连接CPU、存储器、I/O接口,再配置上外设而成

微机组成

微机原理组成:运算器、控制器、存储器、输入设备、输出设备

微机物理结构(实际)组成:主机(主板、CPU、内存条、硬盘、软驱、光驱、显卡、声卡、网卡等)、外设(显示器、打印机、键盘、鼠标等)

微机概念组成:在总线上直接挂接上CPU、内存、I/O接口,外设通过I/O接口实现与计算机连接

微机工作原理

运算器部件是计算机中进行数据加工的部件

ALU运算需2个操作数,分别来自ACC和X寄存器,运算结果保存到ACC及MQ寄存器


控制器完成一条指令的“取指、分析、执行”

控制器由控制单元CU、指令部件、时序部件、PC(Program Counter)、IR等组成

指令部件由指令寄存器IR、程序计数器PC、指令译码器ID组成

PC存放当前欲执行指令的地址,具有计数功能

IR存放当前欲执行的指令


存储器完成程序、数据、地址码的存储,具有读出和写入两种操作

存储器由地址寄存器MAR、数据缓冲寄存器MDR和存储阵列组成

MAR存放CPU要访问的内存地址,CPU对内存单元的读写都要经过MDR传送


I/O系统由I/O接口和I/O设备组成

I/O设备完成程序、数据的I/O操作

I/O接口是I/O设备完成程序、数据I/O操作的中转站

CPU通过对I/O接口状态寄存器的读取,来了解外设的工作状态

I/O设备通过I/O接口接受CU发出的各种控制命令完成相应的I/O操作

8086系统结构

在传统的CPU中,采用串行工作方式,总是先从存储器中取出下一条指令,读出一个操作数,然后执行指令,即:

取指→执指→取指→执指→取指→执指

CPU访问存储器(存取数据或指令)时要等待总线操作的完成

CPU执行指令时总线处于空闲状态

缺点:CPU无法全速运行

解决:总线空闲时预取指令,使CPU需要指令时能立刻得到


在8086中,这些步骤被分配给两个独立的处理单元执行:

指令执行部件(EU)负责执行指令

总线接口部件(BIU)负责取指、读出操作数和写入结果

这两个单元可以独立工作,大多数情况下取指令执行指令重叠进行

即取指过程中,指令执行部件也在工作,加快系统运算速率

这种方式是流水线工作方式


EU在执行指令时不需要访存取指令,只需要从指令队列取指,并分析执行

如果指令执行中需要访存或I/O端口,则EU向BIU发送访存的逻辑地址,BIU根据要求形成访存物理地址,然后访问存储器或I/O端口,取得操作数后送到EU参加运算,必要时再将运算结果写会存储器

EU实际不与外界打交道,所有与外部有关的操作都在BIU控制下完成

8086CPU结构

地址总线是20位

8086CPU内部结构框图

总线接口部件BIU(Bus InterfaceUnit)

提供与外部的接口

数据总线 :双向三态,16位

地址总线 :单向三态,20位

BIU功能

地址形成

访存并取指令

指令排队

读/写操作数

总线控制

BIU组成

段寄存器(段基址寄存器)

段寄存器都是16位

CS(Code Segment):代码段寄存器,存放程序代码段起始地址的高16位

DS(Data Segment):数据段寄存器,存放数据段起始地址的高16位

SS(Stack Segment):堆栈段寄存器,存放堆栈段起始地址的高16位

ES(Extended Segment):扩展段寄存器,存放扩展数据段起始地址的高16位

指令指针寄存器

IP(Instruction Pointer):指令指针寄存器,存指令在代码段中的偏移量,16位

物理地址加法器

\[ 物理地址=段基址\times16+偏移地址 \]

乘16在二进制数的表现为左移4位

即相当于在16位的段基址最低位后添加4个0,再与偏移地址相加,得到20位的地址

例如:

CS=2000H,IP=003AH 则物理地址CS : IP就为CS x 16 + IP,即2003AH

注意:2000H,003AH,2003AH都为16进制,最后的结果2003AH换算为二进制就是5x4=20位,符合20位物理地址


关于CS : IP的解释:

在8086微处理器中,冒号(:)用于连接段寄存器和偏移寄存器,以形成一个完整的内存地址

冒号前面的部分(如CS,DS,SS,ES等)是段寄存器,它存放的是基地址

这个基地址需要乘以16(或者说,向左移动4位),以将它转换为20位的物理地址

然后,将冒号后面的部分(如IP,BX,SI,DI等)加到这个物理地址上,就得到了最终的物理地址

指令队列

存放指令,算是缓冲的作用

空间为6个字节

但不代表只能存6条指令,因为有的指令可能不止1个字节

这些指令队列寄存器通过总线接口单元BIU将后继要执行的指令提前由内存提前调入其内,以加快指令读取速度

利用指令队列寄存器可以构成指令流水线,以提高CPU工作效率

总线控制部件

主要功能是产生总线控制信号,如访问存储器或者I/O端口的读写信号等

总线控制逻辑部件负责对CPU全部外部引脚的操作,核心功能是控制系统总线

当执行单元(EU)需要与I/O设备、存储器进行数据交换时,总线控制逻辑部件会协助EU完成数据交换

BIU工作过程

CS和IP形成20位地址,送地址总线、送出MEM读信号、取指令到指令队列待用(6字节)

当EU取走指令,则自动调整指针并补充读入,如队列满,则空闲

如指令中需访问MEM或IO,则BIU根据EU给出的逻辑地址形成20位物理地址,由BIU负责读/写

IP寄存器,如CALL指令,则把当前指令的下一条指令的IP入栈,并清队列,根据新地址取6个字节待用,JMP相类似

总线控制部件发出总线控制信号,完成MEM或IO的读写

指令执行部件EU(Execution Unit)

提供通用寄存器阵列,都为16位

运算器,16位

EU功能

译码

执行

EU组成

算术运算逻辑部件ALU
标志寄存器FR(Flag Register)

标志寄存器16位

标志寄存器里存的信息被称为程序状态字(Program Status Word,PSW)

9个标志位

状态标志

CF(Carry Flag):进位标志位

CF=1,则本次运算最高位(\(D_{15}\)\(D_{7}\))有进位(加法运算时)或有借位(减法运算时)


PF(Parity Flag):奇偶校验标志位

PF=1,表示本次运算结果中有偶数个“1”;PF=0表示本次运算结果中有奇数个“1”


AF(Auxiliary Carry Flag):辅助进位标志位

AF=1,表示运算结果的8位数据中,低4位向高4位有进位(加法运算时)或有借位(减法运算时)

这个标志位仅在十进制数即BCD码运算中有效


ZF(Zero Flag):零标志位

ZF=1,表示本次运算结果为0,ZF=0,运算结果非0


SF(Sign Flag):符号标志位

SF=1,表示本次运算结果的最高位(\(D_{15}\)\(D_{7}\))为“1”,即符号数运算结果为负,否则SF=0


OF(Overflow Flag):溢出标志位

OF=1,表示本次算术运算结果溢出

溢出:字节运算的结果超出了-128 ~ 127;或者字运算超出了-32768 ~ 32767

二进制表示:

字节运算的范围:10000000 ~ 01111111(8位)

字运算的范围:1000000000000000 ~ 0111111111111111(16位)


例子:

可见,超过范围OF置1,最高位为1时SF置1,产生进位CF置1

控制标志

IF(Interrupt Flag):中断标志位

IF=1,表示允许CPU响应可屏蔽中断

IF=0,表示CPU禁止中断


DF(Direction Flag):方向标志位

串操作指令中,DF=0表示串操作指令地址指针自动增量,即串操作的地址由低地址向高地址进行

DF=1表示地址指针自动减量,即串操作的地址由高地址向低地址进行

DF=0,从低地址到高地址搜索

"串操作"是一种特殊的操作,它可以对在内存中连续存在的数据进行操作。这些操作通常是组合操作,能完成几条指令的功能

详情见“串指令


TF(Trap Flag):单步标志位

TF=1,表示控制CPU进入单步工作方式,这种工作方式,CPU每执行完一条指令就自动产生一次内部中断,在调试程序很有用

通用寄存器

通用寄存器都是16位

分为两组:数据寄存器和指针变址寄存器

数据寄存器

通用数据寄存器既可作为16位寄存器,也可作为8位寄存器,即把16位的寄存器分成高8位和低8位

低8位被命名为AL, BL, CL, DL

高8位被命名为AH, BH ,CH, DH


AX(Accumulator Register):累加器,用于存放参加运算的数据和结果,在乘、除法运算、I/O操作、BCD数运算中有着不可代替的作用

AX的特殊用法:

1
MUL		BL

8位乘法指令,功能为寄存器AL x BL

其中,一个乘数一定放在AL中,另一个乘数可以放在BL中,也可以放在BH, CL等8位寄存器中,最后的乘积一定放在AX中

1
MUL		BX

16位乘法指令,功能为寄存器AX x BX

其中,一个乘数一定放在AX中,另一个乘数可以放在BX中,也可以放在CX, DX等16位寄存器中,乘积一定放在DXAX中,其中DX保存高16位


BX(Base Register):基址寄存器,除了作为数据寄存器,还可存放内存的逻辑偏移地址AX, CX, DX不能,即[BX]这个用法是合法的,但是不能用[AX], [CX], [DX]这样的写法

[ ]这个符号给出了逻辑偏移地址,可以用来算出对应的物理地址,具体解释见“[]逻辑地址

BX的特殊用法:

1
MOV		AL,	[BX]

在汇编语言中,[BX]表示的是一个内存地址,这个地址的偏移量存储在BX寄存器中。例如,如果BX中存放的数是40F6H,那么[BX]就表示的是以“DS * 16 + 40F6H”为地址的内存单元。

当我们在指令中使用[BX]时,我们实际上是在引用BX寄存器中存储的地址指向的内存单元的内容。例如,指令MOV AX, [BX]的功能是将BX中存放的数据作为一个偏移地址,段地址默认在DS中,将DS:BX处的数据送入AX中。

这段汇编代码MOV AL, [BX]的含义是将内存中的数据(以BX内容为地址)传送到AL寄存器里。也就是说,执行这条指令后,AL寄存器的内容会变得与内存 [BX]的内容一样。


CX(Counter Register):数据寄存器,既可作为数据寄存器,又可在串指令和位移指令中计数用

比如C语言中的for(int i=100; i > 0; i--),每次循环i都会自减,执行100次后循环结束,在8086中称为“串操作”

CX的特殊用法:

1
2
3
4
5
6
MOV CX, 5    ; 设置循环次数为5
MOV AX, 2 ; 初始化AX寄存器的值为2

START_LOOP:
ADD AX, AX ; 将AX寄存器的值加倍
LOOP START_LOOP ; 如果CX不为0,减少CX的值并跳转到START_LOOP,否则继续执行下一条指令

LOOP指令对CX做减1操作,并将结果返回到CX中,再判断CX的内容,若CX不为0,则回到标号处重新执行,直到CX为0为止,执行后面的语句,相当于C语言循环控制变量i--0

关于汇编语言中循环控制的方法:

在汇编语言中,循环的实现方式与C语言有所不同。在C语言中,我们可以使用各种循环结构(如for,while,do while等)并且可以自由地增加或减少循环计数器。但在汇编语言中,循环通常是通过特定的循环指令和一个循环计数器(通常是CX或ECX寄存器)来实现的。

例如,汇编语言中的LOOP指令会自动将CX寄存器的值减1,然后检查CX是否为零。如果CX不为零,程序就会跳转到指定的位置继续执行;如果CX为零,LOOP指令就会结束,程序会继续执行LOOP指令后面的代码。因此,这种循环的行为类似于C语言中的i--

但是,汇编语言还提供了其他的循环控制指令,如LOOPE/LOOPZLOOPNE/LOOPNZ,这些指令除了检查CX或ECX是否为零外,还会检查零标志(ZF)。这就为汇编语言的循环提供了更多的灵活性。

总的来说,虽然汇编语言的循环控制方式与C语言有所不同,但它们都提供了强大的循环控制能力。


DX (Data Register):数据寄存器,除了可以作为通用数据寄存器,还在乘除法、带符号数扩展指令中有特殊作用

DX的特殊用法:

1
IN		AL		DX

DX存的内容可以作为端口的I/O地址,如图所示,若DX存的内容为0080H,则上述指令就是从编号为0080H的I/O端口输入一个8为数据给AL,这是常见的I/O操作,用于从外部设备读取数据

DX内容为I/O端口地址的功能是别的寄存器所不具备的


指针变址寄存器

数据寄存器AX, BX, CX, DX中,只有BX可以作为地址指针,考虑到实际编程中需要大量的地址指针,只有BX指示或寻址数组中的元素是不够的,于是8086引入了指针和变址寄存器,由SI, DI, SP, BP组成,特殊功能是存放存储器地址


SI(Source Index):源变址寄存器,用于存放内存的逻辑偏移地址,也可存放数据

DI(Destination Index):目标变址寄存器,用于存放内存的逻辑偏移地址,也可存放数据

SP(Stack Pointer):堆栈指针,用于存放栈顶的逻辑偏移地址

BP(Base Pointer):基址指针,用于存放内存的逻辑偏移地址,跟SS寄存器默认搭配


SI, DI, BP与BX的功能差不多,但SP有明显差异,主要用于指示堆栈栈顶

EU控制器

取指控制及时序控制

EU的工作过程

发出控制信号,从指令队列中取出指令译码,完成执行

算术逻辑部件ALU完成算术运算和逻辑运算,改写PSW

BIU和EU可以并行工作,即流水线技术,但仅当CALL、RET及JMP等指令时,要改变执行顺序时,不能并行工作

8086CPU引脚

8086CPU可以工作在最小模式和最大模式下

由于受到当时制造工艺的限制,部分引脚采用分时复用

这里仅写我认为重要的,不看懂就无法理解8086工作原理的几个引脚,剩下的应该不是很重要,查手册就可以


AD15 - AD0(Address Data Bus):分时复用的地址、数据总线


A19/S6,A18/S5,A17/S4,A16/S3(Address/Status):分时复用的地址、状态线

作为地址线用时,A19 - A16与AD15 - AD0一起构成了访存的20位地址线

CPU访问I/O端口时,A19 - A16保持低电平“0”

作为状态线用时,S6 - S3用来输出状态信息,其中S3和S4表示当前正在使用的段寄存器名

例如S4S3 = 10时,表示当前使用CS寄存器对存储器寻址,或当前正在对I/O端口或中断向量寻址

S5用来表示中断标志状态,与IF状态一致,即S5=0时禁止一切可屏蔽中断,S5=1时允许可屏蔽中断请求

8086工作在最小模式下时,S6恒为0


~BHE/S7(Bus High Enable/Status):总线高位有效信号(三态输出,低电平有效),表示当前高8位数据总线上的数据有效

低8位数据总线选通信号为AD0(偶地址)

~BHE/S7可以用来控制高八位的存储器或I/O接口芯片,以区分和控制存取的是一个字节还是一个字

例如,在偶数地址开始的一个字节存取时,~ BHE/S7为0,AD0为1(此时高8位数据总线上进行字节传送);在奇数地址开始的一个字节存取时,~ BHE/S7为1,AD0为0(此时低8位数据总线上进行字节传送)

8086与8088不同之处

8086 8088
外部数据总线 16位 8位
指令队列 6字节 4字节
Memory和IO空间选择 M/IO IO/M

8086存储器组织

8086CPU的存储器是一个最多寻址1MB的存储空间,系统为每字节分配一个20位的物理地址(对应十六进制地址范围为00000H~FFFFFH)

存储器中,任何两个相邻的字节被定义为一个“字”

在一个字中,每字节都有一个地址,这两个地址中较小的一个被用来作为这个字的地址

线性编址

微型计算机的内存储器以字节为基本单位存储信息,每个字节占用一个存储单元,每个存储单元给定一个唯一的地址,这个地址称为物理地址,物理地址以二进制无符号整数形式从0开始编号,顺序增1

数据存储

一个字的起始地址可以从偶地址开始,如图里的6B07H

一个字的起始地址也可以从奇地址开始,如图里的3E60H

较高地址的字节存的是该字的高8位

较低地址的字节存的是该字的低8位

如00001H存的是6B07H的6B,00000H存的是07;00006H存的是3E60H的3E,00005H存的是60


偶地址开始存储的是“规则字”

奇地址开始存储的是“非规则字”

对于这两种字的解释在“8086存储器组织-分体结构”中有详细解释


总结

存储器中以字节为单位存储信息

每个存储单元有唯一的物理地址

存放顺序:低字节存放在低地址;高字节存放在高地址

分段技术

存储空间的分段

8086系统把1MB的内存空间(地址线是20根,寻址空间为\(2^{20}=1MB\))分为若干个段,要求:

①每段的容量不超过64KB(寄存器只有16根数据线,可寻址\(2^{16}=64KB\)

②段内起始地址必须能被16整除

各段的功能因具体用途而定,可分为代码段、数据段、堆栈段、附加段(对应BIU内的CS, DS, SS, ES段寄存器)

段内起始单元地址的高16位(低4位为0)称为段基址,段内某单元距首单元的位移量称为偏移地址/有效地址

例子:

1234H作为段基址,则段内起始单元地址为“12340H”,多出来的低4位0可以保证段内起始单元地址能被16整除: \[ 12340H = 0\times16^0+4\times16^1+3\times16^2+2\times16^3+1\times16^4 \] 分段技术可以使每个存储单元都可以用两种地址表示:

物理地址是内存中信息存储的实际地址

逻辑地址(段基址 : 偏移地址)是允许在程序中编排的地址

CPU必须按照物理地址对对内存进行寻址,访存时首先把逻辑地址转换为物理地址,然后再对内存单元进行读/写


将段基址与偏移地址放入BIU的物理地址加法器就可以计算出实际物理地址


如每个段都是64K,则最多可分16个段:

\[ 1MB=2^{20}=2^{16}\times2^{4}=64KB\times16个 \]

段与段可以交差甚至重叠:

物理地址的形成

BIU里有4个段寄存器:CS, DS, SS, ES

逻辑地址由段寄存器和段内偏移量寄存器组成 \[ \begin{align*} 物理地址&=段基址\times16+偏移地址\\ &=段寄存器的值\times16+段内偏移量寄存器的值 \end{align*} \]

逻辑地址的来源(段寄存器与段内指针寄存器搭配使用)

CS : IP指向代码段(当前执行的代码的地址)

SS : SP指向堆栈段(SS指向堆栈段首地址,SP指向堆栈段栈顶)

SS : BP,因为后面“基址/变址寻址”用到了这个知识点,就补充在这里:BP默认是和SS搭配

DS : BX(SI, DI)指向数据段(指向数据段中的特定位置)

ES : DI(串操作)指向附加段

分体结构

在8086系统中,存储器采用分体结构,即1MB的存储空间分为两个512KB的存储体,一个存储体中包含偶数地址,另一个存储体包含奇数地址

可见,数据总线的高8位连接偶地址存储器,低8位连接奇地址存储器

地址线仅用到A19 - A1,原因是A0用于区分当前访问的是哪个存储体


8086的有些指令是访问(读或写)字节的,有些指令是访问(读或写)字的

在同一时间,8086存储器中取出来的信息数量总是16位的,而且该16位数据是在存储器中以偶地址开头2字节的内容

当8086要访问字节时,在被读出的16位数据中,只要忽略高8位或者低8位就可以得到所需要的1字节信息,如下图(a)(b)所示:

当8086要访问1个字,而这个字是偶地址起始的,只需要使A0=0, ~BHE=0,就可以一次性访问到该字的内容,如下图(c)所示:

当8086要访问1个字,而这个字是奇地址起始的,情况就很复杂,必须对两个连续的偶地址字做两次存储器访问,每次访问忽略不要的1字节,保留剩余的1字节,然后变换得到一个完整的字信息,如下图(d)所示:

8086编程并不涉及这些细节,一条指令只是访问一个特定的字节或字,细节操作是在处理器的控制下自动实现的

综上,在字访问的情况下,对奇地址存放的字需要进行两次读/写操作,对偶地址存放的字仅需一次读/写操作

为了加快程序的运行速度,希望被访问的存储器的字地址为偶地址,所以偶地址起始的字称为“对准字”或“规则字”,奇地址起始的字称为“非对准字”或“非规则字”

相邻两个单元(字)的读写

偶地址为低字节的字的读写

奇偶存储体都是低电平有效

A0=0, ~BHE=0使得奇偶存储体都有效,再根据A19 - A1获取相应地址

这样数据总线可以一次从奇偶两个存储体读/写16位数据,即一次访存读/写一整个字

奇地址为低字节的字的读写

可见奇地址为低字节的两个连续字节形成的字,物理地址最后几位不像偶地址低字节一样仅差一位,奇地址低字节物理地址后三位有差别,所以不能在一次访存中获取这两个字节

于是就需要两次访存,第一次拿出8位的奇字节,第二次拿出8位的偶字节,这样才能拼出完整的字

一次访存是操作16位的数据,所以每次都会舍弃8位不需要的数据

堆栈

SS指向堆栈段首地址

SP指向当前栈顶

8086的堆栈是“向下增长”的,具体来说,栈的空间是从SS : SP指向的地址开始,向低地址方向延伸

举个例子:假设栈初始化为这样:SS=1000H,SP=0100H

假设这个时候栈为空,什么都没存,则说明这个堆栈段最大就只能存0100H个字节,即从SS : SP开始延伸,最大延伸到SS : 0000H

在8086中,PUSHPOP指令都是对16位寄存器或存储器两单元的操作数来操作的,但是内存空间(包括栈空间)是以字节(8位)为单位进行存储的,所以无论进栈或出栈,都要使用栈空间的两个字节;同时也要注意高地址放高字节,低地址放低字节

此时对栈进行压栈操作,SP的值会减少2个字节,即0100H - 2H = 00FEH

同理,出栈操作,SP的值会增加2个字节,即00FEH + 2H = 0100H

工作过程图解:

8086作业

在某系统中,已知当前(SS)=2360H,(SP)=0800H,请说明该堆栈段在存储器中的物理地址范围。若往堆栈中存放20字节数据,那么SP的内容为什么值?

:(SS)<< 4+(SP)=23600H+0800H=23E00H(见图2-10),堆栈段在存储器中的物理地址范围是23600H~23E00H。若往堆栈中存入20个字节数据,那么SP的内容为0800H-14H=07ECH。(20的十六进制为14H)


已知当前数据段位于存储器的B4000H~C3FFFH范围内,则DS段寄存器的内容为多少?

:段寄存器DS的内容为B400H


若已知当前(DS)=7F06H,在偏移地址为0075H开始的存储器中连续存放6字节的数据,分别为11H、22H、33H、44H、55H和66H。请指出这些数据在存储器中的物理地址。如果要从存储器中读出这些数据,需要访问几次存储器?各读出哪些数据?

:物理地址:7F06H<<4+0075H=7F0D5H(见图2-10),故从7F0D5H起6个地址数据在存储器中的地址为:

7F0D5H:11H

7F0D6H:22H

7F0D7H:33H

7F0D8H:44H

7F0D9H:55H

7F0DAH:66H

由于是从奇地址(7F0D5H)开始存放,至少需要4次读存储器操作,分别是:读出11H,3322H,5544H和66H。(注意:数据的低位在低地址存储,数据的高位在高地址存储。)

具体过程参考“8086存储器组织-分体结构”里面“访问一个字”的说明

8086指令系统

8086指令的特点

1.灵活的指令格式

指令由操作码操作数组成

操作码一般有1~2字节

操作数:参加操作的数或者数所在位置(地址码)

操作数部分一般有0~4字节

2.指令格式的一对多形式

例如JE/JZ 都表示ZF=1时转移

3.较强的运算指令

有专用的乘法、除法指令

4.指令有极强的寻址能力

寻址方式有多种

5.指令有处理多种数据的能力

处理8位/16位、带符号数/无符号数以及压缩BCD数/非压缩BCD数

8086指令寻址方式

“寻址方式”是指令寻找操作数的方法

8086CPU指令中的操作数有一个或两个,个别还有三个

操作数分为分为源操作数目的操作数

比如数据传输指令:MOV 目的操作数, 源操作数

目的操作数不允许立即寻址(立即数)

寻址方式

总共八种寻址方式:

立即寻址

寄存器寻址

直接寻址

寄存器间接寻址

基址/变址寻址

基址+变址寻址

串寻址

I/O端口寻址

立即寻址

立即寻址,操作数就在指令中,紧跟着操作码后面,作为指令的一部分存放在内存的代码中,这种操作数被称为“立即数”

比如:

1
2
MOV    AX, 34EAH
MOV BL, 20

第一句是把16位的十六进制源操作数34EAH存入AX中

第二句是把8位的十进制数20存入BL中(具体几位得看对应的寄存器位数)

注意区分进制,二进制会在末尾写B,八进制会在末尾写O,十六进制会在末尾写H

十进制一般末尾就没有字母

寄存器寻址

如果操作数在寄存器中,指令中源操作数和目的操作数就可以用寄存器寻址

16位的操作数,寄存器可以是AX, BX, CX, DX, SI, DI, SP, BP这些16位的通用寄存器

8位的操作数,寄存器可以是AL, BL, CL, DL, AH, BH, CH, DH这些8位的通用寄存器

比如:

1
MOV     BP,	SP

这一句会把寄存器SP的值存入寄存器BP

1
2
MOV   	AX,	1234H
MOV AL, AH

这两句会把16位的1234H存入AX,再把高8位AH的内容存入低八位AL

也就是说最后AX的内容是1212H

直接寻址

操作数在内存单元里时,在指令中必须给出被访问内存单元的逻辑地址,再被8086转化为物理地址,才能对选中的内存单元读或写

指令中的源操作数或目的操作数采用“直接给出被访问内存单元的逻辑地址”这种方式时,被称为“直接寻址”

如:

1
MOV   AX, [3E4CH]

源操作数采用直接寻址方式,指令中用“[ ]”内给出来的内容是被访问内存单元的逻辑偏移地址,逻辑段地址隐含在DS寄存器内

也就是说,这段指令实现的功能是:取出物理地址为“DS * 10H + 3E4CH”内存单元里的数据,存入AX寄存器中

又比如:

1
MOV  [1234H], BL

目的操作数采用直接寻址方式

将寄存器BL里的8位数据传递到逻辑地址1234H,物理地址为“DS * 10H +1234H”的内存单元中

又比如:

1
MOV  ES:[1234H], BL

给出了ES寄存器,所以目的操作数的物理地址就是“ES * 10H +1234H”

指定一个非默认的段,这种方式叫“段超越


以上例子中,指令的直接地址是用十六进制数形式给出的,但实际编程中,一般用符号地址,即实现定义好的符号表示逻辑偏移地址(相当于一个存了逻辑偏移地址的变量)

比如:

1
2
MOV		AX, BUFF
MOV ES:BUFF, BL

功能和上面一样,只是偏移地址用了BUFF这个变量表示


默认关系:CS:IP、SS:SP、DS:BX、DS:SI、DS:DI、DS和直接偏移

寄存器间接寻址

操作数在内存单元里时,可以使用这种方式

与直接寻址不同的是:内存单元的逻辑偏移地址通过间接的方式给出,即先将被访问内存单元的逻辑偏移地址传送给寄存器,在指令中再由寄存器给出被访问的内存单元的逻辑偏移地址

比如:

1
2
MOV   SI, 61A8H
MOV DX, [SI]

先把61A8H这个16位数给SI寄存器

再用[]给出逻辑偏移地址,即把SI寄存器里存的61A8H当做偏移地址

也就是说,这段指令的作用是把物理地址“DS * 16 + 61A8H”的内容送入DX寄存器中

注:AX、CX、DX不能在寄存器间接寻址中使用,也就是说不能用[AX], [CX], [DX]这样的写法

基址/变址寻址

方便一维表格数据

基址/变址方式提出了“位移量”的概念,即在寄存器间接寻址给出的地址信息上再加一个相对位移量,也称为“相对寻址

位移量是一个带符号的16位十六进制数

当使用寄存器BX或BP(Base Pointer,基址指针,通常与SS搭配)时,称为“基址寻址

当使用寄存器SI或DI时,称为“变址寻址

比如:

1
MOV   CX, 36H[BX]		;基址寻址

这一句也可以写成

1
MOV   CX, [BX+36H]		;基址寻址

所以,源操作数对应的物理地址为“DS * 10H + BX + 36H”

又比如:

1
MOV   -20[BP], AL		;基址寻址

注意:BP寄存器默认和SS寄存器搭配,并且给的是20,是十进制

所以这里的物理地址计算为:“SS * 10H + BP - 14H”

基址+变址寻址

可以使用段超越,适用于二维表

有效地址由三部分组成:“基址寄存器BX” 或 “BP”的内容 + 变址寄存器SI或DI的内容 + 位移量

如:

1
2
MOV   AX,	8AH[BX][SI]  
MOV AX, [BX+SI+8AH]

以上两句等价

物理地址为:“DS * 10H + BX + SI + 8AH”

串寻址

串寻址仅在8086串指令中用

串指令的操作数由其他指令提供,且操作数在内存单元中

源操作数逻辑地址DS : SI,目的操作数的逻辑地址ES : DI

由DF(方向标志),自动调整SI和DI

I/O端口寻址

当操作数在外部设备时使用I/O指令

外部设备的地址叫做端口地址

当外部设备地址用8位寻址时,直接端口寻址方式,这种寻址方式I/O地址仅有256个(00H - FFH,高8位全为0)

当外部设备地址用16位寻址时,寄存器(DX)间接寻址方式


由于外部设备的数据宽度不同,输入指令中目的操作数可以为AL(8位)或AX(16位),输出指令同理

如:

1
IN    AL, 25H

将端口地址为25H的输入设备中8位数据送到AL中

又如:

1
2
MOV		DX, 3E4H
OUT DX, AL

先把03E4H写进DX(用作间接寻址寄存器),再把AL寄存器的内容输出到端口地址为3E4H的外设

8086指令格式

单操作数指令

双操作数指令

三操作数指令(其中一个操作数隐藏在操作码中)

三操作数指令如:

1
ADC		AX, BX

该指令完成操作数AX, BX和CF位(进位Flag,被隐藏)相加

又比如:

1
LDS		SI, [BX]

源操作数DS:[BX]组成的物理地址的连续4个字节分别存入SI和DS(被隐藏)中

8086数据类型

(1)无符号数:8位和16位

(2)带符号数:8位和16位

(3)ASCII:在程序中用单引号括起来

(4)BCD数:压缩BCD和非压缩BCD

压缩BCD码:一个字节表示两位十进制数,如56的压缩BCD就是0101_0110B

非压缩BCD码:一个字节表示一位十进制数,其中高4位为0,低4位存放个位,比如5的非压缩BCD就是0000_0101B

8086指令集

按功能分为六种:数据传输类、算术运算类、逻辑运算类(位操作)、串操作类、程序控制类(CS:IP)、处理机控制类

数据传输指令

数据传输指令实现存储器和寄存器、寄存器和寄存器、AX或AL寄存器与I/O端口之间的字节型或字型数据的传输

数据传输指令共同特点

除了POPFSAHF指令,这类指令的操作结果不会影响FR寄存器中的标志

指令中有两个操作数,目的操作数和源操作数,执行过程为:源操作数→目的操作数;当指令中仅存在一个操作数时,另一个操作数为隐含

通用数据传输指令

8086有4个通用数据传输指令:MOV, PUSH, POP, XCHG,除了XCHG,都可以以段寄存器为操作数,这是其他类型指令无法实现的

传送指令MOV

指令格式为:

1
MOV		目的操作数,	源操作数

MOV指令有三种:

1
2
3
4
5
MOV		Register, oprd		;以寄存器r为目的操作数

MOV mem, oprd ;以存储器mem为目的操作数

MOV seg, oprd ;以段寄存器seg为目的操作数

具体过程如下图:

以寄存器r为目的操作数时,源操作数可以是:寄存器r、存储器mem、段寄存器seg、立即数im(通用寄存器可以从通用寄存器里拿数据)

比如:

1
2
3
4
MOV 	CL, AH			;源操作数为寄存器r
MOV AL, 12H ;源操作数为立即数im
MOV AX, [BX+1] ;源操作数为存储器mem
MOV BX, DS ;源操作数为段寄存器seg


以存储器mem为目的操作数时,源操作数可以是:寄存器r、段寄存器seg、立即数im(没有mem,图上mem不能指向自己,也就是说存储器mem不能从mem拿数据)

比如:

1
2
3
MOV		[BX], 1234H			;源操作数为立即数im
MOV [SI+5], BL ;源操作数为寄存器r
MOV [BX+SI+10], DS ;源操作数为段寄存器seg


以段寄存器seg为目的操作数时,源操作数可以是:寄存器r、存储器mem(没有立即数im和段寄存器seg

这里的seg不包括代码段段寄存器CS

比如:

1
2
MOV DS, AX		;源操作数为寄存器r
MOV ES, [SI+5] ;源操作数为存储器mem


编程时尽量采用寄存器变量


MOV指令需要注意

寄存器不包括IP

目的操作数不允许使用段寄存器CS(指令涉及CS时要小心,执行这条语句后会改变CS的值,使一个新的段成为代码段,但此时IP的值还是没变,所以在改变CS的同时也应该改变IP的值)

目的操作数不能是立即数(只有变量才能被赋值)

立即数不能直接送到段寄存器,如果需要,就通过其他寄存器传送

源操作数和目的操作数的数据类型必须相同:比如指令MOV AL, BX就是错误的,BX是16位,AL只有8位;又比如,指令MOV [BX], 12H也是错误的,因为立即数12H可能是8位,也可能是16位,在MOV [BX], 12H时没有进行说明(用byte ptrword ptr

不允许在两个存储单元中直接传送数据,如果需要,就要通过寄存器传输

源操作数和目的操作数不可同时为段寄存器


为了说明数据是8位的,可用下面的方式:

1
2
MOV		[BX],	byte ptr 12H
MOV byte ptr [BX], 12H

byte ptr说明后面的对象是8位操作数


为了说明数据是16位的,可用下面的方式:

1
2
MOV		[BX],	word ptr 12H
MOV word ptr [BX], 12H

word ptr说明后面的对象是16位操作数,此时word ptr 12H就是0012H

入栈指令PUSH

指令格式为

1
PUSH	oprd

堆栈操作总是对16位数据进行,指令中目的操作数隐含为堆栈

进栈操作把数据传输到以SS为段基址、SP为偏移地址的栈中,具体过程如下:

  1. SP先减2,指向新的栈顶

  2. oprd存入SS : SP指向的栈顶,完成进栈操作

PUSH的源操作数oprd,可以是除了立即数以外的变量,即通用寄存器r,存储器mem,段寄存器seg

比如:

1
2
3
PUSH	BX
PUSH [BX]
PUSH DS
出栈指令POP

指令格式为

1
POP		oprd

目的操作数隐含为堆栈

出栈操作把以SS为段基址、SP为偏移地址的栈顶内容传输到目的操作数中,具体过程如下:

  1. SS : SP指示的栈顶2字节内容传输到目的操作数中

  2. SP加2,指向新栈顶,完成出栈

POP的源操作数oprd,可以是除了立即数以外的变量,即通用寄存器r,存储器mem,段寄存器seg(也不能是CS)

比如:

1
2
3
POP		AX
POP [BX+SI+100H]
POP ES
交换指令XCHG

XCHG(Exchange)

指令格式为:

1
XCHG	r, oprd

完成目的操作数和源操作数的内容互换

上述指令执行时数据交换,属于变量赋值所以oprd可以是通用寄存器r,存储器mem,操作数不能是立即数

源操作数和目的操作数不能同时为mem

段寄存器seg不能作为操作数


如果需要将字型数据偏移地址为2040H单元和2050单元的内容交换:

方案一:MOV

1
2
3
4
MOV AX, [2040H]   
MOV BX, [2050H]
MOV [2050H], AX
MOV [2040H], BX

方案二:XCHG

1
2
3
MOV AX, [2040H]   
XCHG AX, [2050H]
MOV [2040H], AX

方案三:PUSH, POP

1
2
3
4
PUSH [2040H]
PUSH [2050H]
POP [2040H]
POP [2050H]

累加器专用传输指令

8086与外部设备交换数据,必须通过累加器AX或AL传输给I/O端口,外设从输出端口取数据,完成数据输出

反之,则外设需要将数据传到I/O端口,CPU从端口中取数据到AX, AL中,完成数据输入

输入指令IN

指令格式:

1
IN	a, oprd

IN指令把oprd指示的端口内容传输到累加器a

目的操作数为累加器a,代表16位的AX和8位的AL,源操作数是I/O端口地址

如:

1
2
3
4
IN  AL, 80H      ;0080H作为地址,不需要加[]表示地址
IN AL, DX
IN AX, 80H
IN AX, DX

注意:在MOV指令中,表示地址的一般都用[ ]表示逻辑地址,比如[0080H],因为在MOV指令中的0080H作为立即数,所以需要加[ ]表明是地址;而IN指令中,源操作数已经被指定为地址,所以就不需要加[ ]强调地址了

即:IN AL, 80H这句表示的是从端口地址为80H的接口输入数据给AL,采用直接寻址

端口地址若是超过8位,应使用DX间接寻址

输出指令OUT

指令格式:

1
OUT		oprd, a

OUT指令把累加器a的内容传送到oprd指定的端口,目的操作数是I/O端口地址

输出设备的端口地址若是超过8位,应使用DX间接寻址

比如:

1
OUT		3EBH, AX	;错的

这里端口地址为3EBH,超过8位,应该换DX间接寻址

修改为:

1
2
MOV		DX,	3E8H
OUT DX, AX
换码指令XLAT

XLAT(Translate)

指令格式为:

1
XLAT

这个指令没有明显的操作数,因此是隐含寻址,涉及的寄存器有AL和BX

其功能为:以BX的内容加AL的内容构成数据段中的一个地址([BX + AL]),然后从这个地址中取数给AL

所谓“换码”,是指令能够完成1字节的查表转换

有时需要将一种代码换成另一种代码,或在实际问题中采用映射关系来实现转换


因为涉及到BX和AL两个寄存器,所以这条指令执行前,需要改变BX, AL两个寄存器的内容:

1
2
MOV		BX,	表的偏移首地址
MOV AL, 被转换码

XLAT指令不能单独执行,所以有时被称为“复合指令”


例如:建立一个0~9的平方表,求5的平方值

将0~9的平方表建立在偏移地址为2000H的内存中

完成求5的平方指令为:

1
2
3
MOV		BX,	2000H	;指向平方表首地址
MOV AL, 5 ;把5放入AL
XLAT ;执行换码指令,先求BX + AL得到5平方对应的地址,再把这个地址存的数据放入AL

这个例子仅仅为XLAT指令设计

目标地址传输指令

目标地址传输指令是计算有效地址的指令,有效地址是指存储器地址

因此在这类指令中,源操作数必须是存储器

LEA(有效地址传输到寄存器)

LEA(Load Effective Address)

指令格式为:

1
LEA		r,	mem

指令的功能是:取mem指示的地址(这里就是偏移地址)送寄存器r

注意:r是16位

例如:

1
LEA		SI, [2040H]

指令执行后,SI的内容为2040H,也就是取了这个偏移地址,存到SI寄存器中

对比:

1
MOV		SI, [2040H]

MOV指令执行后,SI内容为偏移地址2040H内存单元存的内容,而不2040H这个值


也就是说,LEA这个指令用于取mem的偏移地址,然后存入r中


基本上从DATA段拿数组都是LEA


LDS(装入一个新的物理地址)

LDS(Load DS)

指令格式为:

1
LDS		r,	mem

LDS指令是三操作数指令,其中有两个目的操作数

功能是:将源操作数指示的偏移地址开始的4个连续字节的内容传输到目的寄存器r数据段寄存器DS

r是16位

如:

1
2
3
MOV  BX, 2080H     ;用BX间接寻址
LDS SI, [BX] ;SI←[2080H], DS←[2082H]
MOV AL, [SI] ; AL = 88H

如图,先从“DS * 10H + 2080H”的物理地址中取出内容给SI

再从“DS * 10H + 2082H”的物理地址中取出内容给DS(即更新了DS)

于是再用MOV AL, [SI]时,就是从新的DS开始计算物理地址“DS * 10H + 001EH”,内容为88H,就存入AL

LES(装入一个新的物理地址)

LES(Load ES)

LDS指令类似,只是段寄存器为ES而不是DS

标志寄存器传送指令

FR寄存器是个特殊的寄存器,虽然是16位,但8086中只用了9位

对FR的操作与其他寄存器不一样,指令形式特点为:操作数都是隐含的

其中8位的传输是对SF, ZF, AF, PF和CF操作

LAHF:FR寄存器的低8位存入AH

SAHF:AH存入FR寄存器的低8位

PUSHF:FR寄存器内容入栈

POPF:从栈顶弹出内容存入FR寄存器


利用标志寄存器传送指令,特别是入栈、出栈指令,可以实现对FR寄存器的一些特殊操作要求

比如:对FR寄存器中的TF标志位(第9位)置1,其他位不变

1
2
3
4
5
PUSHF				;FR寄存器内容入栈
POP AX ;FR寄存器内容存入AX
OR AX, 0200H ;或操作,实现第九位(0000_0010_0000_0000B)置1,然后存入AX中
PUSH AX ;更新完的AX入栈
POPF ;更新的AX出栈,弹出到FR寄存器,完成操作

算术运算指令

算数运算指令的共同点

运算指令影响状态标志

参加运算的数可以是无符号整型数、带符号整型数、压缩BCD数和非压缩BCD数

乘法/除法指令中,乘数、被乘数以及除数、被除数、商和余数的存放位置有规定

乘法和除法指令的书写形式有要求

算术加法指令

算术加法ADD

指令功能:目的操作数 ←- 目的操作数 + 源操作数

指令格式:

1
ADD		目的操作数,	源操作数

这个格式可以展开两种加法指令:

以通用寄存器r为目的操作数,源操作数可以是通用寄存器r,存储器mem,立即数im

1
2
3
ADD  AX, BX
ADD BX, [BP+SI+1000H]
ADD AL, 12H

以存储器mem为目的操作数,源操作数可以是通用寄存器r,立即数im

1
ADD  WORD PTR[BX],	5B28H


ADD指令说明:

  1. 指令的目的操作数不能是立即寻址
  2. 两个操作数不能同时为存储器变量
  3. 加法操作中产生的进位影响CF标志
  4. 带符号数相加要考虑溢出
带进位算术加法ADC

ADC(Add with Carry)

指令功能:目的操作数 ←- 目的操作数 + 源操作数 + CF

指令格式:

1
ADC		目的操作数,	源操作数

这个格式可以展开两种加法指令:

以通用寄存器r为目的操作数,源操作数可以是通用寄存器r,存储器mem,立即数im

以存储器mem为目的操作数,源操作数可以是通用寄存器r,存储器mem


ADC指令说明:

  1. 指令中有3个操作数,其中CF是本指令执行前的状态
  2. 需要完成多字节数(如4字节的32位数或更多字节)相加时可以用该指令
  3. 指令的目的操作数不能是立即寻址
  4. 加法操作中产生的进位进入CF标志位
  5. 带符号操作数相加要考虑溢出

例如:完成无符号数 5B68F271H 和 0AC6D5698H 加法操作

操作数为32位,8086寄存器只有16位,所以分两次:先ADD算低16位,产生的进位再跟高16位ADC计算

1
2
3
4
MOV   AX, F271H		;加数的低16位
ADD AX, 5698H ;与被加数的低16位相加,结果存在AX,进位影响CF
MOV DX, 5B68H ;加数的高16位
ADC DX, AC6DH ;与被加数的高16位和CF相加,运算结果存在DX,进位影响CF
加1指令INC

指令功能:目的操作数 ←- 目的操作数 + 1

指令格式:

1
INC		oprd

根据指令的功能,oprd只能是变量,因此可选:通用寄存器r,存储器mem

注:立即数不是变量,所以操作数不能是立即寻址

该指令不影响CF标志

操作数为内存寻址时,因为无法确定是8位还是16位变量,所以需要指明字节型(BYTE PTR)或字型(WORD PTR

如:

1
2
INC  BYTE PTR [BX]  ;BYTE PTR指明字节型
INC AX

很显然,INC AX可以由ADD AX, 1代替,但是ADD指令会影响CF标志位,INC不影响CF标志位,这是主要区别

对压缩BCD数加法操作的结果进行校正DAA

指令功能:对AL寄存器的内容进行十进制数调整,仅修改AL,如果位数过多需要交换再修正

指令格式:

1
DAA

例如:

1
2
3
MOV    AL, 0001_0101B	;15的BCD表示
ADD AL, 0001_0110B ;16的BCD表示
DAA

DAA这个指令就是用于BCD码运算后结果处理

比如这个例子,我给AL先传入的是15的BCD码0001_0101B,换成16进制就是15H

然后再用ADD指令,把16的BCD码0001_0110B(换成16进制就是16H)与AL内容相加,得到的结果为0001_0101B + 0001_0110B = 0010_1011B,换算成十六进制就是15H + 16H = 2BH

很显然,15+16的结果应该是31,如果写成BCD码应该是0011_0001B,换成16进制为31H

现在AL寄存器的内容为2BH,很明显不是我们要的BCD运算结果

这时就可以用DAA指令,就会将AL中的2BH修正为31H,这就是DAA指令的作用

可以看到,一个数的BCD表示写成十六进制和它的十进制完全相同,比如15的BCD码0001_0101B,换成16进制就是15H


经过这个例子的讲解,DAA指令的功能已经很明确了,再来看书上讲的啥b例子

1
2
3
MOV    AL, 85H
ADD AL, 96H
DAA

这里的85H96H都是BCD码,分别代表了十进制的85和96,而不是普通的十六进制133(85H)和150(96H)

85+96=181(81就是最后AL里应该存的值),但是在ADD运算后,AL里存的是1BH(普通十六进制加法:85H + 96H = 11BH,但AL只有8位,所以只能存1BH,并且产生进位,CF=1)

所以DAA这一步处理,就是把AL里的1BH修正为81H(8位的BCD最多表示到99,所以还是产生进位,CF仍旧为1)

DAA指令说明:

  1. 要求操作的数必须是BCD数,即存在AL的数必须是BCD数
  2. 该指令用在压缩BCD数加法操作后,操作数隐含在AL中


注意:DAA指令仅修改AL,如果位数过多需要交换再修正

例如:

1
2
3
4
5
6
ADD	AL,	[BX]	;AL+一个数值元素→AL
DAA ;十进制调整
XCHG AL, AH ;因为DAA只修正AL的值,所以对进位CF的处理需要对AH加法,将AL与AH交换
ADC AL, 0 ;AL+CF→AL
DAA ;十进制调整
XCHG AH, AL ;AL与AH交换

就用BDC码运算46H + 56H = 102H来解释

AX现在值是0046H,[BX]存的是0056H,经过ADD指令,AL存的就是46H + 56H = 9CH

紧接着就是一次DAA修正,会把9CH修正为102H,但是AL只有8位,所以AL新的值就是02H,产生CF=1的进位

这时AX的值就是0002H,但我们想让这个进位加到高位AH上

于是就产生了XCHG AL, AH这句指令,把AH和AL交换,AX现在的值就是0200H

再进行带进位的加法ADC指令,AX的值就是0201H

对这个值再进行BCD修正,这个虽然例子体现不出来

最后把AH和AL再交换一次,AX的值就是0102H,就满足了46H + 56H = 102H


对非压缩BCD数加法操作的结果进行校正AAA

压缩BCD码:一个字节表示两位十进制数,如56的压缩BCD就是0101_0110B

非压缩BCD码:一个字节表示一位十进制数,其中高4位为0,低4位存放个位,比如5的非压缩BCD就是0000_0101B

指令功能:对AL寄存器的内容进行十进制数调整

指令格式:

1
AAA

如:

1
2
3
MOV    AX, 09H	;AH清零,AL中为加数
ADD AL, 07
AAA ;结果在AX中为0106

AAA指令说明:

  1. 要求参加操作的数必须是非压缩BCD数
  2. 该指令用在非压缩BCD数加法操作后,操作数隐含在AL中
  3. 该指令使用了AH寄存器,所以事先应该将AH内容清零

算术减法指令

算术减法SUB

指令功能:目的操作数 ←- 目的操作数 - 源操作数

指令格式:

1
SUB		目的操作数,	源操作数

这个格式可以展开两种减法指令:

以通用寄存器r为目的操作数,源操作数可以是通用寄存器r,存储器mem,立即数im

以存储器mem为目的操作数,源操作数可以是通用寄存器r,立即数im


SUB指令说明:

  1. 指令的目的操作数不能是立即寻址
  2. 两个操作数不能同时为存储器变量
  3. 减法操作中产生的借位影响CF标志
  4. 无符号操作数相减,若CF=1,则结果为补码
  5. 带符号数相减要考虑溢出
带进位算术减法SBB

SBB(Subtract with Borrow)

指令功能:目的操作数 ←- 目的操作数 - 源操作数 - CF

指令格式:

1
SBB		目的操作数,	源操作数

这个格式可以展开两种减法指令:

以通用寄存器r为目的操作数,源操作数可以是通用寄存器r,存储器mem,立即数im

以存储器mem为目的操作数,源操作数可以是通用寄存器r,存储器mem


SBB指令说明:

  1. 指令有3个操作数,CF是指令执行前的借位
  2. 需要完成多字节数(如4字节的32位数或更多字节)相减时可以用该指令
  3. 目的操作数不能是立即寻址
  4. 无符号操作数相减,若CF=1,则结果为补码
  5. 带符号数相减要考虑溢出

例:完成无符号数 5B68F271H 和 0AC6D5698H 减法操作

操作数为32位,8086寄存器只有16位,所以分两次:先SUB算低16位,产生的借位再跟高16位SBB计算

1
2
3
4
MOV   AX, F271H		;被减数的低16位
SUB AX, 5698H ;与减数的低16位相减,结果存在AX,借位影响CF
MOV DX, 5B68H ;被减数的高16位
SBB DX, AC6DH ;与减数的高16位和借位CF相减,运算结果存在DX,借位影响CF
减1指令DEC

指令功能:目的操作数 ←- 目的操作数 - 1

指令格式:

1
DEC		oprd

根据指令的功能,oprd只能是变量,因此可选:通用寄存器r,存储器mem

注:立即数不是变量,所以操作数不能是立即寻址

该指令不影响CF标志

操作数为内存寻址时,因为无法确定是8位还是16位变量,所以需要指明字节型(BYTE PTR)或字型(WORD PTR

例如:

1
2
DEC		CX
DEC WORD PTR [BX]
对压缩BCD数减法操作的结果进行校正DAS

指令功能:对AL寄存器中的内容进行十进制数调整

对非压缩BCD数减法操作的结果进行校正AAS

指令功能:对AL寄存器中的内容进行十进制数调整

比较指令CMP

指令功能:完成两个操作数相减

指令格式:

1
CMP		目的操作数,	源操作数

执行目的操作数 - 源操作数,但是不保存结果

如果结果为0,说明两个数相等,使ZF = 1

如果CF = 1,说明产生借位,则目的操作数 < 源操作数

如果CF = 0,说明没有产生借位,则目的操作数 >= 源操作数

取补指令NEG

指令功能:0 - 目的操作数

指令格式:

1
NEG		oprd

执行0 - oprd -→ oprd


例如:把DX, AX组成的32位数取补,DX为高16位

1
2
3
NEG		DX		; 0 - DX → DX
NEG AX ; 0 - AX → AX
SBB DX, 0 ; DX - 0 - CF → DX

算术乘法指令

无符号数乘法指令MUL

指令功能:完成两个操作数相乘,存入AX(32位时还用到了DX)

指令格式:

1
MUL		oprd

通用寄存器r或存储器mem,不能是立即数

MUL指令说明:

  1. 8位×8位为16位,16位×16位为32位
  2. 乘数和被乘数都不能立即寻址
  3. 乘数或被乘数必须存放在AL或AX中,在指令中隐含AL或AX
  4. 16位运算结果在AX中存,32位运算结果在DX和AX中存


例如:完成3EH×5DH的运算

1
2
3
MOV		AL,	3EH
MOV BL, 5DH
MUL BL ;执行AL×BL存入AX
带符号数乘法指令IMUL

指令功能:完成两个操作数相乘,存入AX(32位时还用到了DX)

指令格式:

1
IMUL		oprd

通用寄存器r或存储器mem,不能是立即数

IMUL指令说明:

  1. 8位×8位为16位,16位×16位为32位
  2. 乘数和被乘数都不能立即寻址
  3. 乘数或被乘数必须存放在AL或AX中,在指令中隐含AL或AX
  4. 16位运算结果在AX中存,32位运算结果在DX和AX中存
非压缩BCD数乘法结果校正AAM

指令功能:完成两个非压缩BCD数乘法结果的十进制数调整

指令格式:

1
AAM

算术除法指令

无符号数除法指令DIV

指令功能:完成AX ÷ 操作数,存入AX

指令格式:

1
DIV		oprd

通用寄存器r或存储器mem,不能是立即数

DIV指令说明:

  1. 用16位除8位,32位除16位,如果被除数不够16位或32位,就进行扩展
  2. 除数和被除数都不能立即寻址
  3. 被除数必须存放在AX或DX:AX中,在指令中隐含
  4. 16位运算的商在AL中存,余数在AH中存
  5. 32位运算的商在AX中存,余数在DX中存


例:完成无符号数3E2CH÷5BH运算:

1
2
3
MOV		AX,	3E2CH
MOV BL, 5BH
DIV BL
带符号数除法指令IDIV

指令功能:完成AX ÷ 操作数,存入AX

指令格式:

1
IDIV		oprd

通用寄存器r或存储器mem,不能是立即数

带符号数字节扩展指令CBW

指令功能:将AL的8位拓展为16位

指令格式:

1
CBW
带符号数字扩展指令CWD

指令功能:将AX的16位拓展为32位

指令格式:

1
CWD
非压缩BCD数除法结果校正AAD

指令功能:BCD校正

指令格式:

1
AAD

位操作指令

位操作指令共同特点

可以按二进制位进行操作

逻辑运算指令按逻辑门电路运算规则

逻辑位移指令有左移右移,移出的位进入CF标志

因移空位的补充方式不同,有多种指令形式

逻辑移位指令中,移动超过1次,则用CL寄存器作为计数器

执行逻辑操作指令(不包括逻辑位移),CF均被清零

逻辑运算指令

逻辑非指令NOT

指令功能:将8位、16位寄存器或存储器内容求反

指令格式:

1
NOT		oprd

操作数可以是通用寄存器r,存储器mem

NOT指令说明:

  1. 目的操作数不能为立即数寻址
  2. 对8位或16位一次性全部取反


例如:

1
2
MOV		AX,	1234H
NOT AX

AX的内容为EDCBH

逻辑与指令AND

指令功能:将8位、16位寄存器或存储器内容和源操作数“与”

目的操作数 ←- 目的操作数 与 源操作数

指令格式:

1
AND		目的操作数, 源操作数

目的操作数可以是通用寄存器r,也可以是存储器mem

当目的操作数为通用寄存器r时,操作数可以是通用寄存器r,存储器mem,立即数im

当目的操作数为存储器mem时,操作数可以是通用寄存器r,存储器mem

AND指令说明:

  1. 目的操作数不能为立即数寻址
  2. 可由源操作数控制,对8位或16位数的某些位进行屏蔽或保留
  3. 目的操作数和源操作数不能同时为存储器mem


例如:保留AL中的低4位,高4位清零

与,遇0出0,遇1不变

1
AND		AL,	00001111B

再例如:AL中有小写字符"a-z"其中之一的ASCII码,将其转换为大写

在ASCII码中,大写字母和小写字母的二进制表示只有第五位(从右往左数)不同。大写字母的第五位是0,而小写字母的第五位是1

比如:

大写A是65:0100_0001B;大写Z是90:0101_1010B

小写a是97:0110_0001B;小写z是122:0111_1010B

所以后5位保持不变,前2位保持不变,只把第五位从1变到0即可:

1
AND		AL,	1101_1111B
逻辑或指令OR

指令功能:将8位、16位寄存器或存储器内容和源操作数“或”

目的操作数 ←- 目的操作数 或 源操作数

指令格式:

1
OR		目的操作数, 源操作数

目的操作数可以是通用寄存器r,也可以是存储器mem

当目的操作数为通用寄存器r时,操作数可以是通用寄存器r,存储器mem,立即数im

当目的操作数为存储器mem时,操作数可以是通用寄存器r,存储器mem

OR指令说明:

  1. 目的操作数不能为立即数寻址
  2. 可由源操作数控制,对8位或16位数的某些位进行置1或保留
  3. 目的操作数和源操作数不能同时为存储器mem


例如:将DX的低八位置1:

或,遇1出1,遇0不变

1
OR		DX,	00FFH
逻辑异或指令XOR

指令功能:将8位、16位寄存器或存储器内容和源操作数“异或”

目的操作数 ←- 目的操作数 异或 源操作数

指令格式:

1
XOR		目的操作数, 源操作数

目的操作数可以是通用寄存器r,也可以是存储器mem

当目的操作数为通用寄存器r时,操作数可以是通用寄存器r,存储器mem,立即数im

当目的操作数为存储器mem时,操作数可以是通用寄存器r,存储器mem

XOR指令说明:

  1. 目的操作数不能为立即数寻址
  2. 可由源操作数控制,对8位或16位数的某些位进行求反或保留
  3. 目的操作数和源操作数不能同时为存储器mem


例如:将AX的D1,D5,D6,D11和D15求反,其余位不变

异或0,则不变,异或1,则求反

1
XOR		AX,	1000_1000_0110_0010B
测试指令TEST

这个指令还挺好用的,特别是搭配JZ指令,因为只涉及到ZF的值,不会改变目的操作数的值

这个在接口技术课设中用了特别多

指令功能:将8位、16位寄存器或存储器内容和源操作数“与”,但不会改变目的操作数的值

指令格式:

1
TEST	目的操作数, 源操作数

目的操作数可以是通用寄存器r,也可以是存储器mem

当目的操作数为通用寄存器r时,操作数可以是通用寄存器r,存储器mem,立即数im

当目的操作数为存储器mem时,操作数可以是通用寄存器r,存储器mem

TEST指令说明:

  1. AND指令寻址方式和运算规则相同,但TEST指令不产生运算结果,仅影响ZF
  2. 常用来判断某位是否是1或0


例如:判断AX的D1是否为1

1
TEST	AX,	0002H

检测ZF标志,如果为0,说明AX和0002H进行与操作最后的结果不是0,则说明D1确实为1;如果ZF为1,说明D1是0

举例来说:若AX为000FH,二进制就是0000_0000_0000_1111B

这个值与0000_0000_0000_0010B做与操作,得到的是0002H,不是0,所以ZF标志位为0,所以D1确实为1

如果AX为0009H,即0000_0000_0000_1001B,与运算的结果就是0000H,结果为0,ZF置1,所以D1不是1

逻辑移位指令

非循环移位指令

算术左移指令 SAL(Shift Arithmetic Left)

算术右移指令 SAR(Shift Arithmetic Right)

逻辑左移指令 SHL(Shift Left)

逻辑右移指令 SHR(Shift Right)

这四条指令格式相同,以SAL为例:

1
2
SAL		oprd,	1	;移动1位
SAL oprd, CL ;移动多位,用CL控制次数

SAL指令说明:

  1. 移动1位,源操作数为1;移动超过1位,用CL寄存器控制移动次数
  2. 算术左移1位,原数据乘2
  3. 移空的位置补0

比如:

1
2
MOV		DL,	0A5H
SAL DL, 1

执行完后,DL存的就是4AH,最高位1移出去,让CF置1


移位指令执行的操作如图所示:

算术右移补的是符号位

逻辑右移补的是0

循环移位指令

不含进位位的循环左移指令 ROL(Rotate Left)

不含进位位的循环右移指令 ROR(Rotate Right)

含进位位的循环左移指令 RCL(Rotate through Carry Left)

含进位位的循环右移指令 RCR(Rotate through Carry Right)


R(Rotate)就是循环移位

S(Shift)就是非循环移位


格式同非循环移位指令

移位位数放在CL寄存器中,如果只移1位,也可以直接写在指令中

循环移位指令只影响标志位CF和OF

功能如图所示:

例如:将AL的高4位与低4位互换

1
2
MOV		CL,	4
ROL AL, CL

串处理指令

串:顺序放在内存中的一组相同类型的数据

串操作:对串中的元素进行相同的操作

串处理指令是针对存储器的操作

串处理指令共同点

  1. 指令特殊寻址方式:源操作数的逻辑地址由DS : SI给出,目的操作数的逻辑地址由ES : DI给出
  2. 存储单元由字型和字节型之分,所以指令助记符有W(Word)或B(Byte)之分
  3. 使用这类指令,存储单元的地址指针是自动移动的(串操作指令自动修改SI和DI,字节±1,字±2),用DF标志位控制指针移动方向:DF=0,地址往增大方向移动;DF=1,地址往减小方向移动
  4. 串的长度由CX给定
  5. 这类指令前面一般可以使用指令前缀(比如重复前缀REPREPNZ),具体见“指令前缀
  6. 这类指令后不带操作数,操作数隐含给定

串指令使用一般方法

CLDSTD指令

因为串指令中使用DF位,所以把标志控制指令提前放在这里了

CLD(Clear Direction Flag)指令:将方向标志位DF清零,即从低地址到高地址

STD(Set Direction Flag)指令:将方向标志位DF置1,即从高地址到低地址

指令格式:

1
2
CLD
STD

串传输指令MOVSBMOVSW

B指Byte

W指Word

指令功能:目的操作数 ←- 源操作数

指令格式:

1
2
MOVSB
MOVSW

源操作数的逻辑地址由DS : SI给出,即操作SI

目的操作数的逻辑地址由ES : DI给出,即操作DI

DF控制SI或DI是增大还是减小

B和W控制SI和DI是加减1还是加减2

串的长度由CX给定(即数据区长度),但是指令执行一次,CX内容不变(所以要循环执行)


例如:用串传送指令,将偏移地址为BUFF1的内存区中200个字型数据,转移到偏移地址为BUFF2的内存区

1
2
3
4
5
6
7
8
9
10
11
12
LEA		SI,	BUFF1		;源操作数地址指针
LEA DI, BUFF2 ;目的操作数地址指针
CLD ;DF=0
MOV CX, 200 ;数据区长度

;*******************************************************************
;MOVSW 如果仅写一个MOVSW,这个操作只会被执行一次,所以需要加一个循环
;*******************************************************************

MOV_LOOP: ;循环MOVSW
MOVSW
LOOP MOV_LOOP ;CX自减,直到CX为0

注:如果仅写一个MOVSW,这个操作只会被执行一次(CX的值不会改变),所以需要加一个循环


指令也可写成:MOVS dest, src

但要求:

srcDS:SI寻址,destES:DI寻址

②传送是字节还是字,由操作数的类型决定

串比较指令CMPSBCMPSW

指令功能:目的操作数 - 源操作数,与CMP指令类似,不会产生结果,只会影响ZF位

指令格式:

1
2
CMPSB
CMPSW

源操作数的逻辑地址由DS : SI给出,即操作SI

目的操作数的逻辑地址由ES : DI给出,即操作DI

指令也可写成:CMPS dest, src

串搜索指令SCASBSCASW

SCASB(Scan String Byte)

SCASW(Scan String Word)

指令功能:AL/AX - 目的操作数

指令格式:

1
2
SCASB
SCASW

源操作数的逻辑地址由DS : SI给出,即操作SI

目的操作数的逻辑地址由ES : DI给出,即操作DI

本指令用于在串中查找指定的信息,要搜索的关键字放在AL(字节)或AX(字)中


例如:

在ES段的偏移1000H开始处存有10个ASCII码。搜索E,若找到则记下搜索次数及存放地址,并在屏幕上显示Y;若未找到则显示N

在屏幕上显示一个字符的指令段如下:

1
2
3
MOV  DL, <字符>
MOV AH, 2
INT 21H

程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MOV  DI, 1000H         	;(DI)←串偏移地址
MOV CX, 10 ;(CX)←串长度
MOV AL, ’E’ ;搜索关键字=’E’,要搜索的关键字放在AL(字节)或AX(字)中
CLD ;置DF=0,从低地址到高地址进行搜索
REPNZ SCASB ;若未找到, 继续搜索
JZ FOUND ;找到, 转至FOUND
MOV DL, ’N’ ;串中无’E’,(DL)←’N’
JMP DONE ;转至DONE
FOUND:
DEC DI ;指针回退
MOV ADDR, DI ;ADDR←’E’的地址
SUB DI, 1000H
MOV NUM, DI ;NUM←搜索次数
MOV DL, ’Y’ ;(DL)←’Y’
DONE:
MOV AH, 2
INT 21H ;显示字符
HLT ;Halt指令,暂停程序

对部分代码的解释:

REPNZ SCASBREPNZ(Repeat While Not Zero)是一个前缀指令,相当于循环执行CX次,同时CX自减,中间如果出现ZF=1就break,否则就正常结束循环

如果出现break的情况,那说明此时ZF一定为1(即SCASB搜索到对应值了),此时就紧接着执行JZ FOUND指令,跳转到FOUND标签

如果没出现跳出循环的情况,JZ FOUND指令就不起作用,紧接着执行的是MOV DL, ’N’指令,按题目要求把N写入DL寄存器,然后执行JMP DONE指令,跳转到DONE标签,执行显示操作


关于FOUND标签第一句:DEC DI:不管SCASB找到没找到,DI一定会根据DF的值变化。在这里DF为0,DI一定会在SCASB执行完以后+1,所以如果DI到了E这里,执行搜索,搜到了,DI还是会自增1,这就是为什么在寻找E地址的时候需要DEC DI

关于MOV ADDR, DIMOV NUM, DI:这里的ADDRNUM都是变量


关于最后的HLT指令:可以理解为C语言程序的system("pause"),处理器进入暂停状态,直到接收到中断信号

串装入指令LODSBLODSW

LODSB(Load String Byte)

LODSW(Load String Word)

指令功能:AL/AX ←- 源操作数

源操作数的逻辑地址由DS : SI给出


每执行一次,将存储单元的内容写入累加器

不影响标志位


指令格式:

1
2
LODSB
LODSW

执行完以后SI会自增,如果是B就自增1次,如果是W就自增2次

其中:

1
2
3
4
5
6
7
8
9
10
LODSB
等价于
MOV AL, [SI]
INC SI

LODSW
等价于
MOV AX, [SI]
INC SI
INC SI

串存储指令STOSBSTOSW

STOSB(Store String Byte)

STOSW(Store String Word)

指令功能:目的操作数 ←- AL/AX

目的操作数的逻辑地址由ES : DI给出


每执行一次,将累加器的内容写入存储单元

不影响标志位


例如:把从A000H开始的2KB内存单元清零

1
2
3
4
5
MOV  DI, A000H		;起始偏移地址
MOV AX, 0 ;将累加器的内容写入存储单元,所以先将0写入AX
MOV CX, 2048 ;2KB
CLD ;低地址向高地址写
REP STOSB ;循环执行

又例如:把1000H开始的100个存储单元填入ASCII码2AH(*)

1
2
3
4
5
MOV  DI, 1000H        ; 首地址
MOV AL, 2AH ; ‘*’
MOV CX, 100 ; 重复执行100次
CLD ; 增量修改DI
REP STOSB

再例如:设在1000H开始存有四个压缩的BCD码12、34、56、78;要求把它们转换为ASCII码存放在3000H开始的单元中

假定DS、ES都已设置为数据段的段基址

源操作数的逻辑地址由DS : SI给出,即LODSB指令将DS : SI的内容Load进累加器AL

目的操作数的逻辑地址由ES : DI给出,即STOSB指令将累加器AL的内容Store进ES : DI

程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MOV  SI, 1000H	;SI←BCD首址,因为LODSB指令需要从DS : SI读取数据
MOV DI, 3000H ;DI←ASCII首址,因为STOSB指令需要把数据写入ES : DI
MOV BX, 4 ;置计数器初值
BBB:
MOV AL, [SI] ;AL←BCD码,第一次取12H
AND AL, 0FH ;屏蔽高4位→02H
OR AL, 30H ;转换为ASCII码→32H
STOSB ;保存结果,将累加器AL的内容写入ES : DI
LODSB ;读取结果,将DS : SI的内容Load进累加器AL
MOV CL, 4
SHR AL, CL ;逻辑右移4位
OR AL, 30H ;得到高4位ASCII码
STOSB ;保存结果
DEC BX ;(BX)←(BX)-1
JNZ BBB ;(BX)≠0,则继续循环

指令前缀

在串操作指令中,串的长度(数据个数)输入进CX,而指令执行时并不对CX进行操作

重复执行这些指令时,需要对CX中的值自减来控制循环的结束

指令前缀就是控制串处理指令循环啊进行,每执行一次,CX内容减1,直到CX内容减到0

如果有些指令还涉及到了ZF标志,则还需要对ZF进行判别

REP前缀

REP前缀就是重复,每执行一次,CX内容减1,直到CX为0退出循环

例如:将偏移地址为BUFF1的内存区中100个字型数据,传送到偏移地址为BUFF2的内存区

源操作数的逻辑地址由DS : SI给出,即操作SI

目的操作数的逻辑地址由ES : DI给出,即操作DI

1
2
3
4
5
LEA		SI,	BUFF1	;源操作数地址
LEA DI, BUFF2 ;目的操作数地址
CLD ;DF=0,从低地址向高地址
MOV CX, 100 ;100个字型数据
REP MOVSW ;字型,用MOVSB

REPZ/REPE前缀

REPZ/REPE前缀往往与CMPS配合使用

REPZ(Repeat While Zero)

REPE(Repeat While Equal)

若执行这样的语句:

1
REPZ	CMPS

每执行一次CMPS指令,CX减1,ZF根据比较的结果,如果相同就置1

若CX≠0且ZF=1,比较继续进行

若CX≠0且ZF=0,比较停止

若CX=0,比较停止

所以这个可以用于判断两个串数据是否相等

REPNZ/REPNE前缀

REPNZ/REPNE前缀往往与SCAS配合使用

REPNZ(Repeat While Not Zero)

REPNE(Repeat While Not Equal)

若执行这样的语句:

1
REPNZ	SCASB

每执行一次SCASB指令,CX减1,根据比较累加器AX(或AL)的值与当前指针指向的值,如果相等则ZF=1

若CX≠0且ZF=0,比较继续进行

若CX≠0且ZF=1,break比较停止

若CX=0,比较停止

所以这个可以用于判断是否找到AX里的值

程序控制转移指令

无条件转移JMP

段内转移

段内直接转移用NEAR PTR

1
2
3
JMP		NEAR PTR	目标地址	;直接转移
JMP CX ;间接转移
JMP WORD PTR [BX] ;间接转移
段间转移

段间直接转移用FAR PTR

1
JMP		FAR PTR		目标地址	;直接转移

条件转移

我在课设里用JZ用的最多,也还挺好用的

JZ/JE:ZF标志为1就转移

JNZ/JNE:ZF标志为0就转移

JC:CF标志为1就转移

JNC:CF标志为0就转移

JS:SF标志为1就转移

JNS:SF标志为0就转移

JO:OF标志为1就转移

JNO:OF标志为0就转移

循环控制指令

LOOP

执行LOOP会使CX减1

用我在接口技术课设里的代码举例:

1
2
3
4
5
6
7
C_16	PROC	NEAR		;标准C,16分音符
MOV CX, 29
C_LOOP_16:
CALL Music_C
LOOP C_LOOP_16
RET
C_16 ENDP

C_16这个程序里用到了这样一段LOOP:

1
2
3
4
MOV		CX,	29
C_LOOP_16:
CALL Music_C
LOOP C_LOOP_16

先给CX一个值,表示循环次数为29次

然后循环里就一直调用Music_C函数,调用29次

结束循环后直接RET

子程序调用和返回指令

注意:写子程序的时候先保护现场,最后返回现场

子程序调用CALL

根据我实际做课程设计的情况看,无论隔多远,都能CALL

所以这里只记录段内直接调用

1
CALL	函数名
子程序返回RET

这个就是高级语言的return

中断指令和中断返回指令

中断指令INT N

中断返回指令IRET

关系运算符

EQ(Equal):相等

NE(Not Equal):不等

LT(Less Than):小于

GT(Greater Than):大于

LE(Less Equal):小于等于

GE(Greater Equal):大于等于

若满足关系,则结果为1

不满足关系,则结果为0

1
MOV	DL,	10H LT 10

10H不小于10,所以结果为0,相当于MOV DL, 0

处理器控制指令

STC:使CF置1

CLC:使CF清0

CMC:使CF取反

STD:使DF置1

CLD:使DF清0

STI:使IF置1

CLI:使IF清0


HLT:暂停

WAIT:等待

ESC:换码指令/交权指令

LOCK:总线封锁

NOP:空操作指令

8086指令集总结

用到的指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
MOV	目的操作数,	源操作数	;源操作数移动到目的操作数

PUSH oprd ;oprd指示的地址16位内容入栈,但不能是立即数

POP oprd ;16位栈顶元素出栈到oprd指示的地址

XCHG r, oprd ;完成目的操作数和源操作数的内容互换

IN a, oprd ;把oprd指示的端口内容传输到累加器a

OUT oprd, a ;把累加器a的内容传送到oprd指定的端口,目的操作数是I/O端口地址

XLAT ;隐含寻址,从[BX + AL]地址中取数给AL

LEA r, mem ;取mem指示的地址(这里就是偏移地址)送寄存器r,r应该是16位

LDS r, mem ;将mem开始的4个连续字节的内容传输到目的寄存器r和数据段寄存器DS中

LES r, mem ;与LDS类似,传输到ES中

LAHF ;FR寄存器的低8位存入AH

SAHF ;AH存入FR寄存器的低8位

PUSHF ;FR寄存器内容入栈

POPF ;从栈顶弹出内容存入FR寄存器

;*********************************************************************************

ADD 目的操作数, 源操作数 ;目的操作数 ←- 目的操作数 + 源操作数

ADC 目的操作数, 源操作数 ;目的操作数 ←- 目的操作数 + 源操作数 + CF

INC oprd ;目的操作数 ←- 目的操作数 + 1,不影响CF标志

DAA ;对压缩BCD数加法操作的结果进行校正

AAA ;对非压缩BCD数加法操作的结果进行校正

SUB 目的操作数, 源操作数 ;目的操作数 ←- 目的操作数 - 源操作数

SBB 目的操作数, 源操作数 ;目的操作数 ←- 目的操作数 - 源操作数 - CF

DEC oprd ;目的操作数 ←- 目的操作数 - 1,不影响CF标志

DAS ;对压缩BCD数减法操作的结果进行校正

AAS ;对非压缩BCD数减法操作的结果进行校正

CMP 目的操作数, 源操作数 ;目的操作数 - 源操作数,但是不保存结果,仅影响ZF和CF

NEG oprd ;取补,0 - oprd -→ oprd

MUL oprd ;完成两个操作数相乘(AX内容乘oprd),存入AX,在指令中隐含AL或AX,oprd不能是立即数

IMUL oprd ;带符号数乘法指令(AX内容乘oprd),存入AX,oprd不能是立即数

AAM ;非压缩BCD数乘法结果校正

DIV oprd ;完成AX ÷ 操作数,存入AX,oprd不能是立即数

IDIV oprd ;带符号数除法指令,完成AX ÷ 操作数,存入AX

CBW ;带符号数 字节扩展指令,将AL的8位拓展为16位

CWD ;带符号数 字扩展指令,将AX的16位拓展为32位

AAD ;非压缩BCD数除法结果校正

;*********************************************************************************

NOT oprd ;将8位、16位寄存器或存储器内容求反

AND 目的操作数, 源操作数 ;目的操作数 ←- 目的操作数 与 源操作数

OR 目的操作数, 源操作数 ;目的操作数 ←- 目的操作数 或 源操作数

XOR 目的操作数, 源操作数 ;目的操作数 ←- 目的操作数 异或 源操作数

TEST 目的操作数, 源操作数 ;目的操作数 与 源操作数,但不会改变操作数的值,只会改ZF

;*********************************************************************************

;***** S为Shift,非循环移位 *****

SAL oprd, 1 ;算术左移1位,补0
SAL oprd, CL ;算术左移多位,用CL控制次数,补0

SAR oprd, 1 ;算术右移1位,算数右移补符号位
SAR oprd, CL ;算术右移多位,用CL控制次数,算数右移补符号位

SHL oprd, 1 ;逻辑左移1位,补0
SHL oprd, CL ;逻辑左移多位,用CL控制次数,补0

SHR oprd, 1 ;逻辑右移1位,逻辑右移补0
SHR oprd, CL ;逻辑右移多位,用CL控制次数,逻辑右移补0

;***** R为Rotate,循环移位 *****

ROL oprd, 1 ;将操作数oprd所有位都向左移,最高位复制到CF和最低位
ROL oprd, CL ;将操作数oprd所有位都向左移CL指定的位数,最高位复制到CF和最低位

ROR oprd, 1 ;将操作数oprd所有位都向右移,最低位移入最高位
ROR oprd, CL ;将操作数oprd所有位都向右移CL指定的位数,最低位移入最高位

RCL oprd, 1 ;把操作数oprd的每一位都向左移,CF复制到最低位,而最高位复制到CF
RCL oprd, CL ;把操作数oprd的每一位都向左移CL指定的位数,CF复制到最低位,而最高位复制到CF

RCR oprd, 1 ;把操作数oprd的每一位都向右移,CF复制到最高位,而最低位复制到CF
RCR oprd, CL ;把操作数oprd的每一位都向右移CL指定的位数,CF复制到最高位,而最低位复制到CF

;*********************************************************************************

;***** 串处理指令用到的标志控制指令 *****

CLD ;将方向标志位DF清零,即从低地址到高地址

STD ;将方向标志位DF置1,即从高地址到低地址

;***** 串处理指令 *****
;***** 源操作数的逻辑地址由DS : SI给出 *****
;***** 目的操作数的逻辑地址由ES : DI给出 *****

MOVSB ;B为Byte,传输一个字节,目的操作数 ←- 源操作数
MOVSW ;W为Word,传输一个字,目的操作数 ←- 源操作数

CMPSB ;目的操作数 - 源操作数,与CMP指令类似,不会产生结果,只会影响ZF位
CMPSW ;目的操作数 - 源操作数,与CMP指令类似,不会产生结果,只会影响ZF位

SCASB ;AL - 目的操作数,用于在串中查找指定的信息,要搜索的关键字放在AL(字节)或AX(字)中
SCASW ;AX - 目的操作数,用于在串中查找指定的信息,要搜索的关键字放在AL(字节)或AX(字)中

LODSB ;AL ←- 源操作数,每执行一次,将存储单元的内容写入累加器
LODSW ;AX ←- 源操作数,每执行一次,将存储单元的内容写入累加器

STOSB ;目的操作数 ←- AL,每执行一次,将累加器的内容写入存储单元
STOSW ;目的操作数 ←- AX,每执行一次,将累加器的内容写入存储单元

;*********************************************************************************

REPZ CMPS ;可以用于判断两个串数据是否相等

REPNZ SCASB ;可以用于判断是否找到AX里的值

;*********************************************************************************

JMP NEAR PTR 目标地址 ;直接转移
JMP CX ;间接转移
JMP WORD PTR [BX] ;间接转移

;***** LOOP, CALL, RET *****
C_16 PROC NEAR ;标准C,16分音符
MOV CX, 29
C_LOOP_16:
CALL Music_C
LOOP C_LOOP_16
RET
C_16 ENDP

STC ;使CF置1
CLC ;使CF清0
CMC ;使CF取反

STD ;使DF置1
CLD ;使DF清0

STI ;使IF置1
CLI ;使IF清0

能用立即数的指令

MOV可以立即数,PUSH, POP, XCHG不行

LEA可以立即数,LDS, LES不行

加减法可以立即数,乘除法不行

ADD, ADC可以立即数,INC不行

SUB, SBB可以立即数,DEC不行

CMP可以立即数,NEG不行

AND, OR, XOR, TEST可以立即数,NOT不行

1
2
3
4
5
6
7
8
9
10
11
12
13
MOV
IN ;立即数用来指明端口
OUT ;立即数用来指明端口
LEA ;立即数用来指定地址
ADD
ADC
SUB
SBB
CMP
AND
OR
XOR
TEST

不能用立即数的指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PUSH
POP
XCHG
LDS
LES
INC
DEC
NEG
MUL
IMUL
DIV
IDIV
NOT
SAL ;这些位移指令,可以移动1次,这里1应该不算立即数
SAR ;把这些位移指令放在“不能立即数”,是因为oprd不能是立即数寻址
SHL
SHR
ROL
ROR
RCL
RCR

隐含/没有操作数的指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
XLAT	;隐含操作数,涉及的寄存器有AL和BX
LAHF
SAHF
PUSHF
POPF ;上面几个都是操作标志寄存器的,隐含操作数
DAA ;没有操作数
AAA ;没有操作数
DAS ;没有操作数
AAS ;没有操作数
AAM ;没有操作数
CBW
CWD
AAD
CLD
STD
MOVSB
MOVSW
CMPSB
CMPSW
SCASB
SCASW
LODSB
LODSW
STOSB
STOSW

指令集作业

分别指出下列指令中的源操作数和目的操作数的寻址方式

1
2
3
4
5
6
7
8
MOV		AX, [SI]			;1
MOV DI, 100 ;2
MOV [BX], AL ;3
MOV [BX][SI], CX ;4
ADD AX, 106H[SI] ;5
PUSH AX ;6
AND DS:[BP], AX ;7
OR AX, DX ;8

答:

目的操作数寻址方式 源操作数寻址方式

(1) 寄存器 寄存器间接

(2) 寄存器 立即

(3) 寄存器间接 寄存器

(4) 基址+变址 寄存器

(5) 寄存器 变址

(6) 寄存器间接 寄存器

(7) 带段超越的寄存器间接 寄存器

(8) 寄存器 寄存器

注:

PUSH指令目的操作数是隐藏的,目的操作数为[SP],因此寻址方式是寄存器间接


形如106H[SI]这样的写法:

当使用寄存器BX或BP(Base Pointer,基址指针,通常与SS搭配)时,称为“基址寻址

当使用寄存器SI或DI时,称为“变址寻址


[BX][SI]这样的是基址+变址


DS:[BP]这样的,指定一个非默认的段,这种方式是段超越

因为BP默认是跟SS搭配的,这里指明了DS段,所以是非默认段,就是段超越

同时也是寄存器间接



已知寄存器(DS)=2000H,(SS)=1500H,(ES)=3200H,(SI)=0A0H,(BX)=100H,(BP)=10H

数据段中变量VAL的偏移地址值为50H

试指出下列各条指令中源操作数的寻址方式是什么?对于存储器操作数,其物理地址是多少?

1
2
3
4
5
6
7
8
9
10
11
MOV		AX, [100H]

MOV CX, ES:[BX]

MOV DX, [BX][SI]

MOV AX, VAL[SI]

MOV BX, 1234[BX]

MOV AX, [BP]

注意BP与SS搭配



指令理解题:判断下列指令有无错误,若有,则改正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(1)PUSH       CL				;错:堆栈指令都是16位,CL只有8位
(2)ADC AX, 0ABH ;对
(3)OUT 3EBH, AX ;错,3EBH这个地址超过8位,要用DX间接寻址
(4)MUL AL, CL ;错,MUL指令的AL应该是隐藏的
(5)MUL AX, 25 ;错,除了AX隐藏以外,MUL指令也不能用立即数
(6)ROL DX, 5 ;错,位移指令的次数应该用CL控制
(7)CALL FAR SUM ;对
(8)POP FR ;错,FR寄存器出栈用POPF
(9)MOV 234H, BX ;错,MOV指令的目的操作数应该给地址,应该是[234H]
(10)INC [SI] ;错,要加BYTE PTR或WORD PTR,因为汇编器无法确定要操作的是一个8位的字节还是一个16位的字
(11)ADD [BX], 456H ;错,跟上一句一样的错误,要加BYTE PTR或WORD PTR
(12)INT 0 ;错,没有中断0这条指令
(13)DIV AX, BX ;错,DIV隐藏AX这个操作数
(14)DEC [BP] ;错,跟10一样的错,要加BYTE PTR或WORD PTR
(15)XLAT BX ;错,XLAT不需要操作数
(16)ADD CX+1 ;错,加法指令应该是两个操作数
(17)DAA AX ;错,DAA不需要操作数

答:

(1)有错。对堆栈操作总是16位的。修改为:

1
PUSH	CX

(2)无错。只是CF的值是不确定的,可能是1,也可能是0

(3)有错。输出设备的端口地址若是超过8位,应使用DX间接寻址。修改为:

1
2
MOV		DX,	3EBH
OUT DX, AX

(4)有错。乘法指令中有AX或AL寄存器是隐含的,故指令中仅出现第二个操作数,若为AL×CL,则改为:

1
MUL	CL

(5)有错。与题(4)有类似错误,即乘法指令其中一个数隐含在AX或AL;同时还有一处错误:乘法指令中不能用立即数。改为:

1
2
MOV   BX, 25
MUL BX

(6)有错。8086指令系统中,移位的次数由CL寄存器控制。修改为:

1
2
MOV	CL, 5
ROL DX, CL

(7)无错。在CALL指令中提供子程序的属性(FAR或NEAR),使汇编程序在汇编过程中更快

(8)有错。寄存器FR是不能单独操作的。修改为:

1
POPF

(9)有错。立即数不能作为目的操作数,因为它不可能存放任何信息。修改为:

1
2
3
MOV	BX,	234H

MOV [234H], BX

(10)有错。该错误主要是针对汇编程序的,由于翻译这条指令时,不知是8位的还是16位的目标码,故要加入宏汇编的综合运算符PTR。修改为:

1
2
3
INC   BYTE PTR [SI]

INC WORD PTR [SI]

(11)有错。与第(10)题有同样的错,因为该指令中没有限定翻译成8位的还是16位的目标码。修改为:

1
2
3
ADD  WORD PTR [BX],	456H

ADD BYTE PTR [BX], 456H

(12)有错。这条指令是没有的,因为被零除一旦产生,自动转去执行中断

(13)有错。除法指令和乘法指令类似,AX是隐含的,指令中不出现。修改为:

1
DIV  BX

(14)有错。除与第(10)题有类似的错误外,还要注意,用BP寄存器间接寻址形成物理地址时,段寄存器是SS而不是DS。修改为:

1
2
3
DEC   WORD PTR [BP]

DEC WORD PTR DS:[BP]

(15)有错。XLAT的操作数是隐含的,它是一条复合指令,在执行该指令之前,将BX指向表的首地址,AL中存入被转换的码,然后才执行该指令。如果用户不按此操作,将得不到希望的结果

(16)有错。加法指令应该是2个操作数。修改为:

1
2
3
INC	CX

ADD CX, 1

(17)有错。调整指令是专门针对AL寄存器的内容进行调整的,故指令中操作数隐含。修改为:

1
DAA



选择题:

(1)带符号数-86在微机中所表示的二进制数值是()。

A. 10101010B B. 01100101B

C. 10011101B D. 11001011B

答案:A

86的二进制为101_0110B

负数在计算机中存的补码:逐位取反加1(或者从右往左数到第一个1,这个1右边的值不变,左边全部取反),按这个计算得010_1010

最后在前面加上符号位1

-86的补码就是1010_1010B


(2)执行“MOV DL, 2AH”和“SHR DL, 1”两条指令后,DL寄存器与CF标志分别为()。

A. DL=10110110B CF=1 B. DL=00110101B CF=0

C. DL=00110100B CF=1 D. DL=00010101B CF=0

答案:D

DL一开始是0010_1010B

SHR DL, 1这条指令是逻辑右移,移出来的位进入CF,补0

移出0进入CF,移位完是0001_0101B


(3)可将AX寄存器中D0、D5、D8和D11位求反,其余位不变的指令是()。

  1. AND AX,921H B. OR AX,910H

  2. XOR AX,0921H D. XOR AX,0110H

答案:C

XOR:异或0,不变;异或1,取反

XOR AX, 0000_1001_0010_0001B


(4)某存储单元的物理地址为3B4FEH,其段地址和偏移地址可分别选为()。

A. 3B4FH和104EH B. 3B40H和00FEH

C. 2A00H和114FEH D. 3B4FEH和0

答案:B

3B4FEH = 3B40H * 10H + 00FEH


(5)两个8位二进制数00110101及10110110做“异或”操作后,寄存器FR的下面3个状态标志分别是()。

A. PF=1,SF=0,ZF=0 B. PF=0,SF=1,ZF=1

C. PF=0,SF=1,ZF=0 D. PF=1,SF=1,ZF=1

答案:C

异或结果为:1000_0011B

最高位符号位为1,则SF=1

结果不为0,ZF=0

PF(Parity Flag)为奇偶标志位,低8位如果有偶数个1,则PF置1;奇数个1,则PF置0

3个1,奇数,PF=0


(6)当执行存储器写指令时,如“MOV [SI], AX”,则CPU的外部管脚状态是()。

  1. ~ WR=1,~ RD=1,M / ~ IO=0 B. ~ WR=0,~ RD=1,M / ~ IO=0

  2. ~ WR=1,~ RD=0,M / ~ IO=1 D. ~ WR=0,~ RD=1,M / ~ IO=1

答案:D

“存储器写”就得出答案:

对存储器操作,M / ~ IO=1(谁有效就操作谁)

写指令,~ WR=0,~ RD=1

汇编语言程序设计

汇编语言语句

指令性语句

由CPU执行

语句格式:方括号是可选段

[标号:] 操作码[操作数1],[操作数2] [;注释]

标号段:以“:”分界,不是必须,提供引用

操作码段:必须

操作数段:可以是常数、寄存器、标号、变量和表达式

注释段:以分号开始;不被汇编程序翻译

指示性语句

由汇编程序执行:伪指令或指示符

语句格式:方括号是可选段

[标识符(名字)] 指示符(伪指令) 表达式 [;注释]

标识符段:伪指令指定;字母、数字或上下划线

指示符段:伪指令;汇编程序执行

表达式段:常数、寄存器、标号、变量、操作符的序列,分为数字表达式和地址表达式

有关属性

段值属性:段起始地址,在段寄存器内,标号的段总是在CS寄存器中

段内偏移量属性:16位无符号数,从段起始地址到该操作数所在位置之间的字节数;当前地址计数器的值($)

类型属性:标号,该标号在本段内引用还是在其他段内引用;段内引用,称之为NEAR,指针长度为2字节;在段间引用,称之为FAR,指针长度为4字节。变量,其类型属性用来指出该变量所保留的字节数,以BYTE、WORD或DWORD

伪指令

伪指令又称伪操作,在汇编程序的指示性语句中作为指示符,在对汇编语言源程序进行汇编期间,是由汇编程序处理的操作

伪指令可以对数据进行定义,为变量分配存储区,定义一个程序或一个过程,指示程序结束等

符号定义语句

等值语句

符号名 EQU 表达式

相当于C语言的#define

比如

1
2
3
4
5
PORT	EQU 1234
BUFF EQU PORT+58
MEM EQU DS:[BP+20H]
COUNT EQU CX
ABC EQU AAA

调用MOV AX, PORT就相当于MOV AX, 1234

一个符号名用EQU语句只允许定义一次,否则会出现语法错误

等号语句

1
2
3
NUM = 35
...
NUM = 7

EQU相似,但可以多次赋值

变量定义语句

语句格式:

1
符号名	DB/DW/DD 表达式

DB:变量为字节型数据(8位,Byte)

DW:变量为字型数据(16位,Word)

DD:变量为双字型数据(32位,Double)


变量被定义以后就有了属性:

数据类型

分配内存单元,如果没有特别指定,就按先后顺序从偏移地址0000H开始存放

低位字节存低地址,高位存高地址

定义一组数据

例如

1
2
BUFF1	DW 1234H, 0ABCDH, 8EH, -79DH
BUFF2 DB 12H, 34H, CDH, 8EH

BUFF1为字型变量,BUFF2为字节型变量

相当于:

1
2
int buff1[4]={0x1234,0x0abcd,0x8e,-0x79d};
char buff2[4]={0x12,0x34,0x0cd,0x8e};

定义一串字符

例如

1
STR	DB	‘Welcome’

定义STR为一个字节型变量,即一个字符占1字节,用ASCII码存放

单引号内是字符串

定义保留存储单元

程序设计中,如果希望将运算结果保存到内存中,设计时就要预留一部分存储单元

也就是说,这些存储单元不需要预先赋值

1
SUM	DW	?, ?

从SUM偏移地址开始,为两个字型数据保留了4字节的内存单元

复制操作

DUP(Duplication)用于预置重复的数值

比如

1
ALL_ZERO	DB	0, 0, 0, 0, 0

等价于

1
ALL_ZERO	DB	5	DUP(0)

段定义语句

比如我现在定义了两个变量

1
2
XX1		DB	12H, 34H
XX2 DW 1200H, 3400H

我就可以用这两个变量了:

1
2
MOV	AL,	XX1
MOV DX, XX2

但是在翻译成机器码时,汇编程序不能确定XX1, XX2是哪一个段,而无法进行正确汇编

XX1, XX2不一定在数据段中,也可以在其他三个段中

而对不同的段,MOV指令机器码是不一样的

所以才出现段定义语句

段定义语句SEGMENT

1
2
3
段名	SEGMENT [定位类型][组合类型][‘类别’]
...
段名 ENDS

但是太复杂,不需要考虑[定位类型][组合类型][‘类别’]这些

直接用这样简单的形式:

1
2
3
段名	SEGMENT
...
段名 ENDS


比如刚刚的问题,就可以结合段定义语句,如此定义:

1
2
3
4
MyData	SEGMENT
XX1 DB 12H, 34H
XX2 DW 1200H, 3400H
MyData ENDS

这样就说明了XX1, XX2是在MyData段中

但是MyData段又在哪呢?还是不知道是在数据段还是其他段

于是就给出了另一个伪指令ASSUME

段说明语句

伪指令ASSUME在汇编时能提供正确的段码,使汇编程序知道程序的段结构

格式如下

1
ASSUME	段寄存器名:段名[,…]

段寄存器可以是CS, DS, SS, ES

段名是刚刚SEGMENT定义的标识符


伪指令ASSUME只是指定某段分配给哪个寄存器,并不能把段地址装入寄存器(因为伪指令不是由CPU执行)

利用伪指令ASSUME,接着对XX1, XX2进行完整定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MyData	SEGMENT
XX1 DB 12H, 34H
XX2 DW 1200H, 3400H
MyData ENDS

MyCode SEGMENT
ASSUME CS:MyCode, DS:MyData
MOV AX, MyData
MOV DS, AX ;最开始还是需要把MyData放入DS
...
MOV AL, XX1
MOV DX, XX2
...
MyCode ENDS

这样就可以了

过程定义语句

注意:写子程序的时候先保护现场,最后返回现场

比如这段程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MUL10 PROC			;一个把AX乘10子程序,入口参数是AX,出口参数AX
PUSHF ;保护现场,保护标志寄存器和BX
PUSH BX
;下面是功能程序段,实现10×AX→AX
ADD AX, AX ;2XX →AX
MOV BX, AX ;2XX →BX
ADD AX, AX ;4XX →AX
ADD AX, AX ;8XX →AX
ADD AX, BX ;8XX+2XX →AX
;恢复现场
POP BX
POPF
RET
MUL10 ENDP

过程中用到了BX,就需要保护BX

并且还需要保护标志寄存器FR,于是最开始就PUSHF,最后POPF

过程就是函数

1
2
3
4
过程名	PROC NEAR/FAR
...
RET
过程名 ENDP

过程名是标识符,是子程序入口的符号地址

NEAR, FAR都是类型属性,NEAR为段内调用,FAR为段间调用

但根据我接口技术课设的情况来看,全写NEAR就行

一般,过程的最后是RET语句

PROCENDP必须成对出现

这就不得不再次提及接口技术课设写的代码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
C_4		PROC	NEAR		;标准C,4分音符
MOV CX, 116
C_LOOP_4:
CALL Music_C
LOOP C_LOOP_4
RET
C_4 ENDP

Music_C PROC NEAR ;C大调对应的标准C,应该延时1911us
CALL W_L ;写0,蜂鸣器响
CALL T100 ;延时1000us
CALL T50 ;延时500us
CALL T20 ;延时200us
CALL T20 ;延时200us
CALL T1 ;延时10us

CALL W_H ;写1,蜂鸣器不响
CALL T100 ;延时1000us
CALL T50 ;延时500us
CALL T20 ;延时200us
CALL T20 ;延时200us
CALL T1 ;延时10us
RET
Music_C ENDP

可以看到,C_4函数里面调用了Music_C函数

结束语句END

END

宏定义与宏调用

接口技术课设都没用到这玩意,我感觉也不是重点

宏调用降低了执行时间


宏调用与子程序调用异同:

都避免相同代码的重复性

都增加了灵活性

宏调用没有减少目标代码的存储空间,子程序调用减少了目标代码的存储空间

宏调用降低了执行时间

DOS功能调用

DOS功能调用号:所有子程序从1号开始顺序编号

调用过程为:

<1>DOS功能调用号送AH寄存器

<2>如果需要,按要求给定输入参数(有些无)

<3>写入中断指令“INT 21H

<4>调用结束,按功能使用其输出参数


例如曾经用过的显示函数:

在屏幕上显示一个字符的指令段如下:

1
2
3
MOV  DL, <字符>	;显示字符放入DL
MOV AH, 2 ;DOS功能号为2
INT 21H ;通过AH里的值调用DOS功能

显示一个字符串同理,放入DX

1
2
3
MOV  DX, <字符串>	;显示字符串地址放入DL
MOV AH, 9 ;DOS功能号为9H
INT 21H ;通过AH里的值调用DOS功能

再比如程序结束代码:

1
2
3
MOV	AX,	4C00H	;4CH就是程序结束
MOV AH, 4CH ;这两句功能一样
INT 21H

PPT上汇编的例子

基本上从DATA段拿数组都是LEA

顺序执行

作业题会用到这个例子,留下印象比较好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
DATA	SEGMENT
BLOCK DW 0ABCDH
BUFF DD ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA
MOV DS,AX
MOV DX,BLOCK ;先把数据段放入DS
MOV AX,DX
AND AX,0F0FH
AND DX,0F0F0H ;对AX和DX进行与操作,此时AX内容为0B0DH,DX内容为A0C0H
MOV CL,4
SHR DX,CL ;对DX右移4位,结果是0A0CH
LEA BX,BUFF ;将BUFF的偏移地址移到BX
MOV [BX+0],AL ;0DH
MOV [BX+1],DL ;0CH
MOV [BX+2],AH ;0BH
MOV [BX+3],DH ;0AH
MOV AX,4C00H
INT 21H
CODE ENDS
END START

这段程序先把数据段放入DS

再BLOCK放入AX与DX

然后对AX和DX进行与操作

此时AX内容为0B0DH,DX内容为A0C0H

然后对DX右移4位,结果是0A0CH(注意:移位针对的二进制,十六进制的一位就代表二进制4位)

将BUFF的偏移地址移到BX

然后从BUFF指定的地址开始,依次存入0DH, 0CH, 0BH, 0AH

分支结构

判断MEMS单元数据,将结果存入MEMD单元

如果数据>0,结果为1,如果数据<0,结果为-1,如果数据=0,结果为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
MY_D SEGMENT
MEMS DB 08H
MEMD DB ?
MY_D ENDS
MY_C SEGMENT
ASSUME DS:MY_D,CS:MY_C
START:
MOV AX, MY_D
MOV DS, AX
MOV AL, MEMS ;取MEMS的数据进入AL
CMP AL, 0 ;检查是否为0
JZ ZERO ;如果是0就跳转到ZERO
TEST AL, 1000_0000B ;最高位为符号位,TEST检测
JZ NOT_NEG ;如果TEST与运算的结果为0,说明最高位不是1,即非负
MOV AL, -1 ;如果没有在上一步跳转,则说明为负
JMP DONE
ZERO:
MOV AL, 0
JMP DONE
NOT_NEG:
MOV AL, 1
JMP DONE
DONE:
MOV MEMD, AL ;根据上面对AL的赋值,直接把AL的值存入MEMD单元
MOV AX, 4C00H
INT 21H
MY_C ENDS
END START

解释:

这段代码是我自己写的

我用来判断负数的方式是

1
2
3
MOV	AL,	MEMS 	;取MEMS的数据进入AL
TEST AL, 1000_0000B ;最高位为符号位,TEST检测
JZ NOT_NEG ;如果TEST与运算的结果为0,说明最高位不是1,即非负

我看PPT还提供了一种方式判断是不是负数

1
2
3
4
MOV	AL,	[BX] ;取数据
ADD AL, 0 ;做ADD运算,影响标志SF
JNS AA1 ;是正数,转移
INC DL ;是负数,统计加1

挺有意思

循环结构

编程统计BUFF缓冲区中负数的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DATA SEGMENT
BUFF DB 67H,9EH,-6AH,0ABH,6DH
MEM DB ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX, DATA
MOV DS, AX
MOV CX, 5 ;控制循坏次数
LEA BX, BUFF ;设置缓冲区指针
XOR DL, DL ;统计计数器DL清零
NEXT:
MOV AL, [BX] ;取数据
ADD AL, 0 ;做运算,影响标志
JNS AA1 ;SF为0,是正数,转移,相当于continue的功能
INC DL ;SF为1,是负数,统计加1
AA1:
INC BX ;移动指针,BX++
LOOP NEXT ;循坏控制
MOV MEM,DL ;保存统计结果
MOV AX,4C00H
INT 21H
CODE ENDS
END START


编程统计AX寄存器中“1”的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CODE SEGMENT
ASSUME CS:CODE
START:
MOV CX, 16 ;循环控制次数
XOR DL, DL ;统计计数清零
CMP AX, 0 ;AX的内容为0吗?
JZ DONE ;是0,结束循坏
BB1:
SHL AX,1 ;否则移动AX,移出去的1进入CF
ADC DL,0 ;利用ADC加CF的特性,统计“1”的个数
LOOP BB1
DONE:
MOV AX, 4C00H
INT 21H


在BLOCK内存区中有一串字符,试编程统计“%”之前的字符个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
DATA SEGMENT
BLOCK DB ‘ANDEPO139%WR’
COUNT EQU $-BLOCK
MEM DB 0
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX, DATA
MOV DS, AX ;把DATA放入DS
MOV SI, OFFSET BLOCK ;这句是把BLOCK的偏移地址移到SI,和LEA SI, BLOCK功能完全一样

;*****************
;上面的MOV SI, OFFSET BLOCK可以完全替换成LEA
;LEA SI, BLOCK
;*****************

MOV CX, COUNT
LOOP1:
MOV AL,[SI] ;取字符
CMP AL,‘%’ ;是‘%’吗
JZ DONE ;是,结束循坏
INC BYTE PTR MEM ;否,统计值加1
INC SI ;移动指针
LOOP LOOP1 ;继续循坏
DONE:
MOV AX,4C00H
INT 21H
CODE ENDS
END START

子程序

例:用子程序结构编写寄存器AX内容乘10,结果仍在AX中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	XX	EQU	1000
CODE SEGMENT
ASSUME CS:CODE
START:
MOV AX, XX ;把AX赋值为1000=03E8H
CALL MUL10 ;调用把AX内容乘10子程序
MOV AX, 4C00H
INT 21H

MUL10 PROC ;一个把AX乘10子程序,入口参数是AX,出口参数AX
PUSHF ;保护现场,保护标志寄存器和BX
PUSH BX
;下面是功能程序段,实现10×AX→AX
ADD AX, AX ;2XX →AX
MOV BX, AX ;2XX →BX
ADD AX, AX ;4XX →AX
ADD AX, AX ;8XX →AX
ADD AX, BX ;8XX+2XX →AX
;恢复现场
POP BX
POPF
RET
MUL10 ENDP
CODE ENDS
END START

注意保护现场


例:用子程序结构编写程序统计BX和DX中1的个数,结果分别放在CL和CH中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
CODE SEGMENT
ASSUME CS:CODE
START:
MOV BX, 1234H ;设置BX的值
CALL COUNT
MOV CL, AL
MOV DX, 8432H ;设置DX的值
MOV BX, DX ;把DX内容传送给BX,因为COUNT程序只检查BX里的1
CALL COUNT
MOV CH, AL
MOV AX, 4C00H
INT 21H
;子程序COUNT的功能是统计BX中1的个数
COUNT PROC ;AL存的是1的个数
PUSH CX
PUSHF
MOV AL, 0 ;初始化AL为0
MOV CX, 16 ;BX是16位
COUNT1:
SHR BX, 1 ;右移BX,移出位进入进位标志CF
JNC COUNT2 ;检测CF
INC AL ;CF为1,AL加1
COUNT2:
LOOP COUNT1
POPF
POP CX
RET
COUNT ENDP
CODE ENDS
END START


例:编写一个子程序,完成一个2位十六进制数到ASCII码的转换

子程序名:CONHA

功能:将2位十六进制数转换成ASCII码(比如1FH转为49_70,即0011_0001_0100_0110B

输入参数:AL = 待转换的数

输出参数:BX = 转换好的ASCII码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
CODE	SEGMENT
ASSUME CS:CODE

CONHA PROC FAR
PUSHF
MOV AH, AL ;先给AH备份一个相同的数
LOW:
AND AL, 0FH ;处理第一个十六进制数,屏蔽掉AL的前4位
MOV CL, AL LT 0AH ;判断是否为数字,条件为比0AH小
CMP CL, 1 ;如果AL现在存的是0-9,就是数字,就进入LOW_NUM
JZ LOW_NUM
JMP LOW_LETTER ;如果AL现在存的是A-F,就是字母,进入LOW_LETTER
LOW_NUM:
ADD AL, '0' ;给数字加'0',就转换为ASCII
MOV BL, AL ;第一个十六进制数的ASCII码放入BL
JMP HIGH ;跳转到HIGH
LOW_LETTER:
ADD AL, 'A' ;给字母加'A',就转换为ASCII
MOV BL, AL ;第一个十六进制数的ASCII码放入BL
JMP HIGH ;跳转到HIGH
HIGH:
AND AH, F0H ;处理第二个十六进制数,屏蔽掉AH的后4位
MOV CL, 4
SHR AH, CL ;给AH存的数右移4位,如果AH本来为10H,位移后为01H
MOV CH, AH LT 0AH ;判断是否为数字,条件为比0AH小
CMP CH, 1 ;如果AH现在存的是0-9,就是数字,就进入HIGH_NUM
JZ HIGH_NUM
JMP HIGH_LETTER ;如果AH现在存的是A-F,就是字母,进入HIGH_LETTER
HIGH_NUM:
ADD AH, '0' ;给数字加'0',就转换为ASCII
MOV BH, AH ;第二个十六进制数的ASCII码放入BH
JMP END ;跳转到END
HIGH_LETTER:
ADD AH, 'A' ;给字母加'A',就转换为ASCII
MOV BH, AH ;第二个十六进制数的ASCII码放入BH
JMP END ;跳转到END
END:
POPF
RET
CONHA ENDP
CODE ENDS

代码解释:

这是我自己写的,我只会用JZ,所以写的比较麻烦,不过功能应该是能实现

设计实例

例:在偏移首地址为ARRAY的内存区有100个字型数据,要求将数组的每个元素加1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DATA SEGMENT
ARRAY DW 100DUP(?) ;定义100个字型随机数
DATA EDNS
CODE SEGMENT
ASSUME DS:DATA,CS:CODE
START:
MOV AX, DATA
MOV DS, AX ;DATA放进DS段
LEA BX, ARRAY ;设置数组首地址指针
MOV CX, LENGTH ARRAY ;数组数据长度
AA1:
INC WORD PTR[BX] ;指定为字型数加1
ADD BX, 2 ;BX指针+2(因为是字型数据)
LOOP AA1
MOV AH, 4CH
INT 21H
CODE ENDS
END START


例:有一个100个元素的BCD数组。编写程序对数组元素求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
DATA SEGMENT
XBCD DB 12H,34H,......98H ;定义100个BCD数
YBCD DW ? ;求和结果放在YBCD中
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX, DATA
MOV DS, AX ;DATA放进DS段
LEA BX, XBCD ;设置寄存器BX为数组XBCD地址指针
MOV CX, 100 ;数组长度送给寄存器CX
MOV AX, 0 ;计算结果在AX中,初始值为0
AGAIN:
ADD AL, [BX] ;AL+一个数值元素→AL
DAA ;十进制调整
XCHG AL, AH ;因为DAA只修正AL的值,所以对进位CF的处理需要对AH加法,将AL与AH交换
ADC AL, 0 ;AL+CF→AL
DAA ;十进制调整
XCHG AH, AL ;AL与AH交换
INC BX ;指针加1
LOOP AGAIN ;循环
MOV YBCD, AX ;计算结果存入变量YBCD中
MOV AX, 4C00H
INT 21H
CODE ENDS
END START

注:DAA指令只修改AL寄存器,但是仅仅8位的AL是绝对不满足这道题的要求的

所以就出现了中间这段代码:

1
2
3
4
5
6
ADD	AL,	[BX]	;AL+一个数值元素→AL
DAA ;十进制调整
XCHG AL, AH ;因为DAA只修正AL的值,所以对进位CF的处理需要对AH加法,将AL与AH交换
ADC AL, 0 ;AL+CF→AL
DAA ;十进制调整
XCHG AH, AL ;AL与AH交换

就用BDC码运算46H + 56H = 102H来解释

AX现在值是0046H,[BX]存的是0056H,经过ADD指令,AL存的就是46H + 56H = 9CH

紧接着就是一次DAA修正,会把9CH修正为102H,但是AL只有8位,所以AL新的值就是02H,产生CF=1的进位

这时AX的值就是0002H,但我们想让这个进位加到高位AH上

于是就产生了XCHG AL, AH这句指令,把AH和AL交换,AX现在的值就是0200H

再进行带进位的加法ADC指令,AX的值就是0201H

对这个值再进行BCD修正,这个虽然例子体现不出来

最后把AH和AL再交换一次,AX的值就是0102H,就满足了46H + 56H = 102H


例:有两个无符号字节型数组,设数组元素个数相等,编程将数组中的对应元素相加,结果存入另一个内存区

作业也有一个类似的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
DATA SEGMENT
M1 DB 20 DUP(?)
M2 DB 20 DUP(?)
M3 DW 20 DUP(0) ;M3是Word型,即2字节
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX, DATA
MOV DS, AX ;DATA加到DS
LEA SI, M1 ;添加M1数组地址
LEA DI, M2 ;添加M2数组地址
LEA BX, M3 ;添加M3数组地址
MOV CX, 20 ;指定循环次数为数组长度
AA1:
MOV AL, [SI] ;M1数组取数放入AL
ADD AL, [DI] ;M2数组取数与AL相加,相加可能会产生进位,这个CF进位要加到高地址(M3为Word型,两字节)上
MOV [BX], AL ;AL相加后的8位结果存入BX
ADC BYTE PTR[BX+1],0 ;结果的进位与高地址字节相加,BYTE PTR指明从BX+1的地方拿1个字节
INC SI
INC DI ;SI和DI只用加1,因为是字节型
ADD BX, 2 ;BX加2,因为是字型
LOOP AA1
MOV AX, 4C00H
INT 21H
CODE ENDS
END START

注:虽然做加法很简单,但是容易忽略一点:8位相加会产生进位,这个进位就需要存起来,与后面的进位加起来得到最后的高位

因此声明结果数组的时候M3 DW 20 DUP(0),声明的是DW,Word型,2字节16位

每次把进位的CF加到这个高字节上就行

ADC BYTE PTR[BX+1],0,用BYTE PTR[BX+1]指明取出高字节,把CF加到这里就行


例:将寄存器AL中高、低字节交换

1
2
3
4
5
6
7
8
9
10
11
CODE	SEGMENT
ASSUME CS:CODE
START:
MOV AL, 1234H
MOV AH, AL
MOV CL, 4
ROL AL, CL ;循环位移即可实现
MOV AH, 4CH
INT 21H
CODE ENDS
END START


例:将AX中的内容按相反顺序存入BX中

将AX左移出来的数右移进BX即可

AX左移出来的数移进CF,BX这边用有CF的右移位即可

1
2
3
4
5
6
7
8
9
10
11
12
13
CODE	SEGMENT
ASSUME CS:CODE
START:
MOV AX, 1234H
MOV CX, 16
ROTATE_LOOP:
SHL AX, 1
ROR BX, 1
LOOP ROTATE_LOOP
MOV AH, 4CH
INT 21H
CODE ENDS
END START


例:编程将将以“$”结束的字符串中的小写字母改为大写字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
DATA	SEGMENT
STR DB 'QWertyuiop!$'
DATA ENDS

CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
LEA BX, STR
ENDLESS_LOOP:
MOV AL, [BX]
CMP AL, '$'
JZ DONE ;循环直到遇到$结束
MOV CL, AL GE 'a' ;如果大于'a',就是小写字母
CMP CL, 1
JZ CHANGE ;是小写字母就跳转CHANGE
JMP NEXT ;不是小写字母就跳转NEXT
CHANGE:
MOV DL, 'a'
SUB DL, 'A' ;将'a'-'A'的差值存入DL
SUB AL, DL ;将小写字母减去差值
MOV [BX], AL ;将修改完的字母放回去
NEXT:
INC BX
JMP ENDLESS_LOOP ;一直循环
DONE:
MOV AH, 4CH
INT 21H
CODE ENDS
END START


例:编写程序,将0—9的数字转换成所要求的密码

明码和密码的映射关系位:

0 1 2 3 4 5 6 7 8 9
2 7 1 6 9 0 8 3 4 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
DATA	SEGMENT
TABLE DB 2,7,1,6,9,0,8,3,4,5 ;密码表
VAL1 DB 8,1,4,7,5 ;被加密码
VAL2 DB 0,0,0,0,0 ;解密码
DATA ENDS

CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX ;DATA入DS
LEA BX, TABLE ;密码表的地址存入BX 基本上从DATA段拿数据地址都是LEA
LEA SI, VAL1
LEA DI, VAL2
MOV CX, 5
MY_LOOP:
MOV AL, [SI] ;取被加密码
XLAT ;BX+AL地址取值,替换AL,BX地址为密码表地址,AL就是被加密码
MOV [DI], AL ;解密码保存
INC SI
INC DI
LOOP MY_LOOP
MOV AH, 4CH
INT 21H
CODE ENDS
END START


例:从键盘输入两个1位十进制数,求两数之和且在屏幕上显示结果

键盘输入DOS功能编号为:1

输入到AL中,为ASCII码

屏幕显示DOS功能编号为:2

DL的ASCII码输出到屏幕

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CODE	SEGMENT
ASSUME CS:CODE
START:
MOV AH, 1 ;DOS调用输入第一个数到AL
INT 21H
MOV BL, AL ;保存输入的第一个数到BL
MOV AH, 1 ;DOS调用输入第二个数到AL
INT 21H
ADD AL, BL ;两个ASCII码相加
AAA ;调整加法结果位非压缩BCD数
MOV DL, AL
ADD DL, '0' ;将数字转换为对应的ASCII码
MOV AH, 2 ;DOS输出到屏幕
INT 21H
MOV AX,4C00H
INT 21H
CODE ENDS
END START

感觉这段代码还是有问题,不过这之后再说了,这个例子只是让我们知道DOS调用:1是键盘输入到AL,2是DL输出到屏幕

汇编作业

编写8086汇编语言程序,将寄存器AX的高8位传送到寄存器BL,AX的低8位传送到寄存器DL

1
2
3
4
5
6
7
8
9
CODE	SEGMENT
ASSUME CS:CODE
START:
MOV BL, AH
MOV DL, AL
MOV AX, 4C00H
INT 21H
CODE ENDS
END START


试统计9个数中偶数的个数,并将结果在屏幕上显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
DATA	SEGMENT
ARR DB 1,2,3,4,5,6,7,8,9
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX ;DATA入DS
LEA BX, ARR
MOV CX, 9
XOR DL, DL ;用DL计数,先清零DL
MY_LOOP:
MOV AL, [BX]
TEST AL, 0000_0001B ;和01H与,如果结果为0,说明是偶数
JZ EVEN ;结果为0,偶数,跳转EVEN
JMP ODD ;结果不为0,奇数,跳转ODD
EVEN:
INC DL
JMP NEXT
ODD:
JMP NEXT
NEXT:
INC BX
LOOP MY_LOOP
ADD DL, '0' ;将DL内容转换为ASCII
MOV AH, 2H
INT 21H
MOV AH, 4CH
INT 21H
CODE ENDS
END START


完成10个压缩BCD数相加,且将结果显示出来

如果是10个两位压缩BCD数相加:

多字节显示到显示屏:DOS调用号为9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
DATA	SEGMENT
XBCD DB 12H,23H,34H,45H,56H,67H,78H,89H,90H,77H
YBCD DW ? ;加法结果
DISPBUFF DB 0,0,0,0 ;显示区缓冲,4个字节,用于DOS调用显示多个数
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
LEA BX, XBCD ;BCD数组放入BX
XOR AX, AX ;结果放入AX,初始化清零
MOV CX, 10 ;循环次数
MY_LOOP:
ADD AL, [BX] ;取值直接加到AL
DAA ;BCD修正
XCHG AH, AL ;交换AL和AH
ADC AL, 0 ;进位CF加到AH
DAA ;再修正
XCHG AH, AL ;这样进位就成功加到AH
INC BX
LOOP MY_LOOP ;循环结束后,AX存的就是BCD加法的和,也是BCD码形式
MOV YBCD AX ;结果保存
;*************因为显示到屏幕上是用ASCII码的形式******************
;*************所以需要对AX的4位数都进行ASCII转换*****************
;*************先把AX的4位分别存储,再加'0'转换ASCII**************
;*************把4位分出来可以见PPT第一个例子*********************
MOV BX, AX ;举例:如果AX和BX都是1234H
AND AX, 0F0FH ;与运算后AX为0204H
AND BX, F0F0H ;与运算后BX为1030H
MOV CL, 4
SHR BX, CL ;BX内容右移4位,现在BX为0103H
ADD BH, '0'
ADD BL, '0'
ADD AH, '0'
ADD AL, '0' ;4个数进行ASCII转换
LEA SI, DISPBUFF ;显示区缓冲地址放入SI
MOV [SI], BH ;1的ASCII
MOV [SI+1], AH ;2的ASCII
MOV [SI+2], BL ;3的ASCII
MOV [SI+3], AL ;4的ASCII
LEA DX, DISP ;DISP地址放入DX
MOV AH, 9 ;调用DOS的9号功能:显示多字符
INT 21H
MOV AH, 4CH
INT 21H
CODE ENDS
END START

如果是10个4位压缩BCD数相加

你妈的,这个好几把麻烦,考试不可能考这么难,我宣布不看这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
DATA	SEGMENT
SBUFF DW 1234H, 5678H, 9321H,… ;定义10个4位BCD数
DBUFF DW ?, ? ;加法结果区
COUNT EQU (DBUFF-SBUFF)/2 ;数据长度
DATA ENDS

CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
LEA SI, SBUFF ;数据区地址指针
LEA DI, DBUFF ;结果区地址指针
XOR DL, DL ;保存加法进位寄存器
MOV AX, 0 ;清空AX
LOP1:
MOV BX, [SI] ;取4位的BCD数
ADD AL, BL ;先做低2位加
DAA ;调整
MOV [DI], AL ;存低2位临时结果
MOV AL, AH ;做高2位加
ADC AL, BH ;考虑上一步加法进位
DAA
MOV [DI+1], AL ;存高2位临时结果
MOV AL, DL ;统计上一次的进位
ADC AL, 0
DAA ;调整进位位十进制数
MOV DL, AL ;保存进位待下次加法用
MOV AX, [DI] ;取低字节加法结果备下次加法用
LOOP LOP1 ;循坏执行加法、调整
ADD DI, 2
MOV [DI], DL
MOV AX, 4C00H
INT 21H
CODE ENDS
END START

你妈的,这个好几把麻烦,考试不可能考这么难,我宣布不看这个


编写程序,将一串混乱的字母按从小到大的顺序排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
DATA	SEGMENT
STR DB 'QWERTYUIOPAZAZA'
LIST DB 26 DUP(0) ;桶排序的桶,字母表有26个字母,所以长度为26
RESULT DB 15 DUP(0) ;结果字符串,跟原字符串等长度,15
DATA ENDS

CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
LEA SI, STR ;待排序字符串
LEA DI, LIST ;桶数组
MOV CX, LENGTH STR
MY_LOOP:
MOV AL, [SI] ;拿字符
SUB AL, 'A' ;字符 - 'A'算出偏移存进AL
INC [DI+AL] ;偏移算出桶下标,对应数值++
INC SI ;字符下标++
LOOP MY_LOOP
;*********经过循环,得到的LIST就存了每个字母出现的次数*******
;*********遍历这个LIST数组,将非零元素输出到结果字符串*******
LEA SI, LIST ;桶数组
LEA DI, RESULT ;结果字符串
MOV CX, 25 ;循环次数
MOV DL, 0 ;控制结果字符串下标
OUTPUT_LOOP:
MOV AL, [SI] ;取出桶中的值到AL
CMP AL, 0 ;测试是否为0
JZ OUT ;如果为0,跳过
MOV BL, SI ;SI为桶的下标
ADD BL, 'A' ;算出应该存放的字母,存到BL
DECREASE_AL_LOOP: ;这个循环是用来给结果字符串加相同字母
MOV [DI+DL] BL
INC DL
DEC AL
CMP AL, 0
JZ OUT
JMP DECREASE_AL_LOOP
OUT:
INC SI ;桶下标加1
LOOP OUTPUT_LOOP ;循环结束后,RESULT就是排序后的结果字符串
LEA DX,RESULT ;DOS调用显示
MOV AH, 9
INT 21H
MOV AH, 4CH
INT 21H
CODE ENDS
END START

总线和存储

总线分类

总线结构

单总线结构:分时调用占用

双总线结构:面向CPU和面向主存

多总线结构

总线基本功能

存储器分类

总线作业

在8086系统中,试用8K×8位的EPROM 2764、8K×8位的静态6264和74LS138译码器,构成一个16KB的ROM(从F0000H开始)和16KB的RAM(从C0000H开始),设8086工作于最小模式。画出硬件连接图,写出ROM和RAM的地址范围

答:

起始地址、容量都告诉你了,这个题还是很容易的

16KB,需要2片8K*8位,两个都是

要构成一个16KB(16K×8)位的ROM,需要两片EPROM 2764(8K×8位)

再计算地址,16K,就是\(2^4\times2^{10}=2^{14}\),则需要14根地址线

起始地址F0000H为:

如果占用14根地址线,就是A0-A13,也就是说,A19到A14是不变的

可以利用这4个1和2个0来连接138

两片存储芯片,分别存偶地址和基地址,则可以用A0来控制偶地址存储器和基地址存储器

刚好,有A15A14这两个0,结合能变的A0,就是000和001,完美符合两种地址选择

剩下的,就是能让芯片正常工作了:

另一个同理:

不变的地址:

最后连接:

IO

IO接口:把外设连接到总线上的一组逻辑电路的总称。实现外设与主机之间的信息交换

IO接口要解决的问题

IO接口功能

IO接口编址方式

统一编址

独立编址

统一编址

独立编址

8086采用的就是独立编址

但是地址线和存储器共用

接收外设信息

查询方式

中断方式

DMA

查询

中断

DMA

DMA有专门的DMA控制器进行控制

8253计时器/定时器芯片

可编程计数器核心电路就是一个事先可以设置计数常数的计数器,其记录脉冲方式和记满“溢出”方式都可以通过程序设定

如何实现定时

软件方法:用一段程序实现延时

利用程序循环延迟指定时间

缺点:延时精度,降低了CPU利用率

硬件方法:设计定时/计数器电路

利用脉冲计数在设定的时间输出定时信号

8253概况

内部有3个独立的16位的定时/计数器(通道),可对3个外设实现定时或计数

每个通道6种工作方式

24引脚双列直插式

最高计数频率2MHz

TTL电平兼容

单电源+5V供电

8253引线图

具体引脚如下:

~CS为片选信号

9,10,11为通道0,同理13,14,15为通道1,16,17,18为通道3

连接至系统端的主要引线

注意地址顺序:先是计数器012,再是控制寄存器

这个地址顺序与编程有关,题目可能告诉你的是8253接口地址范围,比如0120H~0123H

记住最后一个是控制寄存器就行

计数通道的主要引线

每个通道均相同

CLK,时钟脉冲输入,计数器的计时基准

GATE,门控信号输入,控制计数器的启停(相当于让CLK信号进入计数器的开关)

OUT,计数器输出信号,不同工作方式下产生不同波形

8253内部结构

厂家把计数器电路集成在“通道”中,计数器012就是这3个通道


程序员视点:

计数器有3个,每一个计数器内部包括16位初值寄存器,16为计数寄存器(减法计数器)

控制寄存器有1个,存放控制命令字,仅写不能读

占用4个地址——3个计数器,1个控制寄存器


数据总线缓冲器是CPU与8253交换信息的必经之路

8253定时/计数工作过程

工作过程

1.设置8253的工作方式

2.设置计数初值到初值寄存器

3.第一个CLK信号使初值寄存器的内容置入计数寄存器

4.以后每来一个CLK信号,计数寄存器减1

5.减到0时,OUT端输出一特殊波形的信号

注:以上计数过程中还受到GATE信号的控制


首先,外电路的脉冲信号通过CLK进入通道,如果GATE有效,使CLK上的脉冲信号进入计数器计数,8253计数器工作在减1状态,每输入一个计数脉冲,计数器的值减1

当计数器计数到0的时候,OUT信号有效,通知外设计数器产生溢出

在通道工作过程中,CPU可以随时通过对端口的读/写,读取计数器的数据

通道内部

在8253中,每个通道内部设置一个16位计数器,可进行二进制或BCD码计数

采用二进制计数时,最大计数值为0FFFFH

采用BCD码计数时,最大计数值为9999

与此计数器相对应,每个通道内部设置一个16位计数值锁存器,必要时用于锁存计数值

每一个计数器内部如图:

计数值的设置

当一个通道用作计数器时,应将要计数的次数预置到该通道的计数器中

当用作定时器时,从CLK输入一固定频率的时钟脉冲,再根据要求定时的时间算出定时所需计数值(时间常数),并预置到计数器中

计数值与定时时间、CLK端时钟脉冲信号周期的关系如下: \[ 计数值=\frac{定时时间}{时钟脉冲周期} \]

比如我现在输入8253的时钟频率是2MHZ(\(2\times10^6\)HZ)

时钟脉冲周期就是\(\dfrac{1}{2\times10^6HZ}=0.5\times10^{-6}s=0.5\mu s\)

我需要随便一个计数器,启动计数10ms后OUT输出高电平

单位换算:\(1s=1\times10^{3}ms=1\times10^{6}\mu s=1\times10^{9}ns\) \[ 计数值=\frac{10ms}{0.5\mu s}=20000 \] 所以就给计数器赋值20000即可,至于工作方式的选择,下面再细讲

计数启动方式

程序指令启动——软件启动

外部电路信号启动——硬件启动

软件启动的GATE保持高电平写入计数初值后的第2个CLK脉冲的下降沿开始计数

注:软件启动时GATE保持高电平,如果之后工作过程中GATE变成低电平,则说明计数暂停


硬件启动的GATE会有上升沿对应CLK脉冲的下降沿开始计数

这些与8253的工作方式有关

8253工作方式

总共6种工作方式

编号从0到5

0,4是软件启动

1,5是硬件启动

2,3是软件硬件启动

只有方式2和3自动重复计数

方式0——计数结束中断

方式0的作用就是用户可以在设定时间上产生中断信号

软件启动,不自动重复计数(计数初值一次性使用有效)

控制字写入控制寄存器后,OUT端变低电平(一直保持低信号)

计数初值再写入通道后计数器就开始工作

计数结束OUT输出高电平


工作波形如下:

可见,方式0的GATE要一直为1才能正常工作,不然就暂停

方式1——可重复触发的单稳态触发器

所谓单稳电路,就是在输入的激励下产生固定宽度的脉冲电路

硬件启动,不自动重复计数(如果再给一个GATE上升沿,会重新计数)

控制字写入控制寄存器后,OUT端变高电平

初始计数值写入通道后并不开始计数工作,而是等触发信号到来,即GATE上升沿

计数开始OUT端变为低电平

计数结束后又变高(类似一个低脉冲的效果)


工作波形如下:

如图,计数已结束,但是再来一个GATE上升沿仍会重新计数

如图,计数未结束时再来一个GATE上升沿,则会重新计数

方式2——频率发生器

软、硬件启动,自动重复计数

装入初值后OUT端变高电平,计数到最后一个CLK时(即计数到1)OUT输出一个CLK时钟的负脉冲,并连续重复此过程

输入信号是周期性脉冲信号,输出信号也是周期性脉冲信号,从OUT输出

方式2实际上是一个可编程的分频电路,把输入信号分频后以脉冲的形式输出,分频系数是用户事先对通道计数器写入的初始计数值


工作波形如下:

方式3——方波发生器

软、硬件启动,自动重复计数

装入初值后OUT端变高电平

然后OUT连续输出对称方波,即同一周期内,前一半为高电平,后一半为低电平

如果计数初始值N为奇数,则前\(\dfrac{N+1}{2}\)个CLK里OUT为高,后\(\dfrac{N-1}{2}\)个CLK里OUT为低


工作波形如下:

方式4——软件触发选通

软件出发实际上就是CPU通过指令触发一个选通信号给外设,选通信号在触发后设定时间点上发出

软件启动,不自动重复计数

装入初值后输出端维持高电平

计数结束输出一个CLK宽度的负脉冲


工作波形如下:

方式5——硬件触发选通

硬件启动,不自动重复计数

OUT端波形与方式4相同


工作波形如下:

各种工作方式总结

方式0(计数结束中断)

计数过程中,GATE端应保持高电平

每写入一次初值计数一个周期,然后停止计数

OUT端输出是一个约(N+1)TCLK宽度的负脉冲(写入控制字时变低,直到计数为0时升高,从头到尾算是一个负脉冲)

计数过程中可随时修改初值重新开始计数

方式1(单稳态触发器)

门控信号GATE端的跳变触发计数,可重复触发

若下一次GATE上升沿提前到达,则OUT端负脉冲拉宽为两次计数过程之和

计数过程中写入新初值不影响本次计数

方式2(频率发生器)

GATE为计数的控制信号:GATE变低计数停止,再变高时的下一个CLK下降沿,从初值开始重新计数

每个计数周期结束时(减到1时),OUT端输出一个TCLK宽度的负脉冲

计数过程自动重复进行

计数过程中修改初值不影响本轮计数过程

方式3(方波发生器)

OUT输出方波,前半周期为高,后半周期为低,一半一半

计数过程中修改初值不影响本半轮计数过程

其余的与方式2类似

方式4(软件触发选通)

计数过程中,GATE端应保持高电平

每写入一次初值,计数一个周期,然后停止计数

每个计数周期结束时(减到0时),OUT端输出一个TCLK宽度的负脉冲

计数过程中修改初值不影响本轮计数过程

方式5(硬件触发选通)

写入初值时,GATE端应保持低电平

GATE每出现一次正脉冲,计数一个周期,然后停止计数

每个计数周期结束时(减到0时),OUT端输出一个TCLK宽度的负脉冲

计数过程中修改初值不影响本轮计数过程

GATE信号功能表

8253控制字

用于确定各计数器的工作方式

8253必须先初始化才能正常工作

每个计数器都必须初始化一次

CPU通过OUT指令把控制字写入控制寄存器

控制字格式

8位控制字

控制字包括:指定计数器,对通道计数器的读写方式,指定通道工作方式,通道计数器计数时采用的数制信息(BCD或二进制)

每个寄存器都要记住

从高到底分别为:

SC1, SC0, RL1, RL0, M2, M1, M0, BCD

前两个SC选择了计数器

两个RL用于确定读/写操作的方式,同时也是计数长度的选择,00锁存,01低8位,10高8位,11先低后高两个字节,即计数值为16位

M2M1M0指定工作方式,其中方式2和3的编码很特殊,不考虑M2位

最低位的BCD指定是否BCD计数

8253初始化顺序

先写方式控制字,按012的顺序

写计数值低8位后紧接着写同一个计数器的计数值高8位

比如:

FF04H, FF05H, FF06H分别对应计数器012的地址

FF07H为控制器地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MOV   DX,	FF07H		;控制器地址
MOV AL, 36H ;0011_0110B
OUT DX, AL ;计数器0,先低后高16位,方式3,二进制
MOV AL, 71H ;0111_0001B
OUT DX, AL ;计数器1,先低后高16位,方式0,BCD
MOV AL, 0B5H ;1011_0101B
OUT DX, AL ;计数器2,先低后高16位,方式2,BCD
MOV DX, 0FF04H ;换地址到DX
MOV AL, 0A8H ;1010_1000B
OUT DX, AL ;写计数器0的低8位
MOV AL, 61H
OUT DX, AL ;写计数器0的高8位
MOV DX, 0FF05H ;换地址
MOV AL, 00H
OUT DX, AL ;写计数器1的低8位
MOV AL, 02H
OUT DX, AL ;写计数器1的高8位
MOV DX, 0FF06H ;换地址
MOV AX, 0050H
OUT DX, AL ;写计数器2的低8位
MOV AL, AH
OUT DX, AL ;写计数器2的高8位

总结:用到哪些计数器就写对应的控制字,先写控制字

然后再写对应的计数值

8253与系统的连接图

注意,138译码器只连A15到A2

因为A1A0直接连8253的A1A0

如何读出当前计数值

第1种方法——在计数过程中读计数值

先锁存当前计数值,再用两条输入指令将16位计数值读出


第2种方法——停止计数器再读

用GATE信号使计数器停止,再规定RL1和RL0的读写格式,然后读出

扩展定时/计数范围

当定时长度不够时,可把2个或3个计数通道串联起来使用,甚至可把多个8253串联起来使用

例如:CLK频率为1MHz,要求在OUT1端产生频率1Hz的脉冲

计数值设置为: \[ \frac{定时时间}{时钟脉冲周期}=\frac{1}{\frac{1}{1\times10^6}}=1\times10^6 \] 但是计数值最大设置是FFFFH,即65535

\(1\times10^6\)比65536大,只用一个计数器位数设置不够

这时可将计数器0、1串联,工作方式都均为方式3,计数初值均为1000

8253应用举例

采用8253作定时/计数器,其接口地址为0120H~0123H

输入8253的时钟频率为2M HZ

计数器0: 每10ms输出1个CLK脉冲宽的负脉冲

“每10ms”,自动重复,1个CLK的负脉冲,即方式2,设置计数值为\(\dfrac{10\times10^{-3}}{\frac{1}{2\times10^{-6}}}=2\times10^{4}\)

计数器1: 产生10KHz的连续方波信号

方波,一眼方式3,设置计数值为\(\dfrac{\frac{1}{10\times10^3}}{\frac{1}{2\times10^{-6}}}=\dfrac{2\times10^{6}}{10\times10^3}=200\)

计数器2: 启动计数5ms后OUT输出高电平

启动后低电平,结束后高电平,方式0,计数值设置为\(\dfrac{5\times10^{-3}}{\frac{1}{2\times10^{-6}}}=10^4\)

初始化顺序:

先写控制字,按照012的顺序写控制字

计数器0的控制字:0011_0100B

计数器1的控制字:0101_0110B

计数器2的控制字:1011_0000B

8253作业

1.

某系统中8253芯片的端口地址为FFF0H-FFF3H

定义计数器0工作在方式3,CLK0=2MHz,要求输出OUT0=1KHz方波

定义计数器1工作在方式4,用OUT0作计数脉冲,计数值为1000

计数器1计到0,向CPU发中断请求,CPU响应这一中断后继续写入计数值1000,重复开始计数,保持1秒钟向CPU发出一次中断请求

请编写初始化程序,并画出硬件连接图

答:

涉及到中断,就需要8259A,所以初始化也需要考虑8259

但是题目里没有明确指明8259的地址,和对应的中断类型号,所以就可以任取

比如:我规定8259地址为20H-21H,中断类型号为E0H-E7H,中断用8259的IR0

题目规定说CPU响应中断,把1000再写入计数器,则说明中断程序的功能就是再把1000写入计数器,很容易


8253地址:

1111_1111_1111_0000B

1111_1111_1111_0011B

分析一下地址:8253的地址不变的是从A2-A15,所以就从这里入手,用138选通唯一的信号出去,这里就用Y0输出:

如图,A3A2都是0,所以用这两个信号选通Y0即可,剩下的就让138能够工作


8259地址:

20H-21H,不变的就是A1到A15,而且0很多,只有A5一个固定为1

硬件连线图:按题目要求,OUT0输出接到CLK1的输入,OUT1输出接到8259的IR0,剩下的正常相连:

然后就是初始化程序:

分析控制字:

计数器0,则控制字最开头两位是0,即控制字的D7D6为00

计数长度:\(\dfrac{\frac{1}{1k}}{\frac{1}{2M}}=\dfrac{2\times10^6}{1\times10^3}=2\times10^3\),2000,换成二进制就是0111_1101_0000B,可以用除2余数倒着写得到;因为超过了8位,所以需要先低后高写入计数值,即控制字的D5D4为11

规定了计数器0在工作方式3,所以D3D2D1为011

最后没说是否BCD,就不用,D0为0

计数器0的控制字就是:0011_0110B


计数器1,则控制字最开头两位是01,即控制字的D7D6为01

题目明确指明计数器1的计数值为1000,换成二进制就是0011_1110_1000B;因为超过了8位,所以需要先低后高写入计数值,即控制字的D5D4为11

题目明确规定计数器1工作方式为方式4,则D3D2D1为100

最后没说是否BCD,就不用,D0为0

计数器1的控制字就是:0111_1000B


8253初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MOV	DX,	FFF3H		;8253控制口地址
MOV AL, 0011_0110B ;计数器0控制字
OUT DX, AL
MOV AL, 0111_1000B ;计数器1控制字
OUT DX, AL
MOV DX, FFF0H ;计数器0地址
MOV AL, 1101_0000B ;写2000的低八位
OUT DX, AL
MOV AL, 0000_0111B ;写2000的高八位
OUT DX, AL
MOV DX, FFF1H ;计数器1地址
MOV AL, 1110_1000B ;写1000的低八位
OUT DX, AL
MOV AL, 0000_0011B ;写1000的高八位
OUT DX, AL

因为题目用到了8259,所以也需要对8259初始化

题目对8259没有过多约束,所以很容易

ICW1:我觉得可以用边沿触发,指定结束方式(即写ICW4),所以就为0001_0011B

ICW2:指定中断类型号,我规定IR0的中断类型号就是E0H所以ICW2就为1110_0000B

没用级联,所以不写ICW3

ICW4:最普通的中断结束方法,就是0000_0001B,普通结束方式

OCW1:我不需要中断屏蔽,不写

OCW2:我不需要改变中断优先级,也不需要中断结束EOI,不写

OCW3:不需要屏蔽和读出控制字,不写

所以,初始化顺序就是:写ICW1, ICW2, ICW4没了


1
2
3
4
5
6
7
8
MOV	DX,	20H			;A0=0,写ICW1
MOV AL, 0001_0011B ;写ICW1,边沿触发
OUT DX, AL
MOV DX, 21H ;A0=1,写ICW2, ICW4
MOV AL, 1110_0000B ;写ICW2,指定中断类型号为E0H
OUT DX, AL
MOV AL, 0000_0001B ;写ICW4,普通结束方式
OUT DX, AL


然后需要把IR0对应的中断服务子程序放入IVT中,不过在此之前,我们需要先定义这个程序,因为要知道名字

中断服务程序,首先需要保存一系列现场,比如FR,DS,还有用到的寄存器;其次,要关中断,用CLI指令,执行完内容后,开中断,弹出对应内容,最后记得结束中断服务程序,IRET

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MYINT	PROC	FAR
PUSHF
PUSH DS
PUSH AX
PUSH DX
CLI ;关中断
MOV DX, FFF1H ;计数器1地址
MOV AL, 1110_1000B ;写1000的低八位
OUT DX, AL
MOV AL, 0000_0011B ;写1000的高八位
STI ;开中断
POP DX
POP AX
POP DS
POPF
IRET ;记得中断返回
MYINT ENDP

现在就可以把这段程序放入IVT了:

具体过程很容易,保护DS,关中断,把0放入DS,把中断类型号E0H乘4放入SI,把段偏移放入[SI],把段基址放入[SI+2],开中断

1
2
3
4
5
6
7
8
9
10
11
CLI
PUSH DS
MOV AX, 0
MOV DS, AX ;0放入DS
MOV SI, 4 * E0H ;中断类型号乘4
LEA AX, MYINT
MOV [SI], AX ;偏移地址放入[SI]
MOV AX, SEG MYINT
MOV [SI+2], AX ;段基址放入[SI+2]
POP DS
STI


2.

若8253芯片的端口地址为D0D0H-D0D3H,时钟频率为2MHz。现利用其分别产生周期为10微秒的方波、每隔1mS和1S产生一个负脉冲,试画出其与系统的电路连接图,并编写包括初始在内的程序

答:

这个题只有8253,所以比较容易

方波:用方式3,计数值为\(\dfrac{10\times10^{-6}}{\frac{1}{2\times10^6}}=20\)

每隔:说明重复,用方式2或3,指定是负脉冲,就是方式2

每隔1ms:方式2,计数值为\(\dfrac{1\times10^{-3}}{\frac{1}{2\times10^6}}=2\times10^3=2000\)

每隔1s:方式2,计数值为\(\dfrac{1}{\frac{1}{2\times10^6}}=2\times10^6\),超过了65536

所以这个就得级联的形式,可以用每隔1ms的输出接到每隔1s的输入上,再计数就是\(\dfrac{1}{1\times10^{-3}}=1\times10^{3}=1000\)


所以,计数器0产生方波,工作在方式3,计数值为20,所以只需要写低8位即可,控制字为0001_0110B

计数器1每隔1ms产生一个负脉冲,工作在方式2,计数值为2000,需要先写低八位再写高8位,控制字为0111_0100B

计数器2每隔1s产生一个负脉冲,工作在方式2,计数值为1000,需要先写低八位再写高8位,控制字为1011_0100B

并且,计数器1的输出接给计数器2的输入


地址分析:

不变的是A2-A15,能让138工作就行

还是用低位过CBA,变成000,选择Y0导通

程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MOV	DX,	D0D3H		;控制器地址
MOV AL, 0001_0110B ;计数器0控制字
OUT DX, AL
MOV AL, 0111_0100B ;计数器1控制字
OUT DX, AL
MOV AL, 1011_0100B ;计数器2控制字
OUT DX, AL
MOV DX, D0D0H ;计数器0地址
MOV AL, 20 ;写20
OUT DX, AL
MOV DX, D0D1H ;计数器1地址
MOV AL, 1101_0000B ;2000的低8位
OUT DX, AL
MOV AL, 0000_0111B ;2000的高8位
OUT DX, AL
MOV DX, D0D1H ;计数器2地址
MOV AL, 1110_1000B ;写1000的低8位
OUT DX, AL
MOV AL, 0000_0011B ;写1000的高8位
OUT DX, AL

8255并行可编程接口芯片

其实CPU可以直接和外设相连,不用8255也可以

但是CPU只是发送信号,驱动能力很低(电流很低),接很多外设CPU扛不住

于是可以在CPU和外设之间加一块8255,既可以传信号,也可以增加驱动能力,还可以可靠传输

8255概况

8255A是一种通用的可编程并行I/O接口芯片,具有3个独立的带锁存或缓存的数据端口,可与外设并行进行数据交换

用户可以用程序选择多种操作方式,可为CPU与外设之间提供并行输入/出通道

各端口内具有中断控制逻辑,在外设与CPU之间可用中断方式进行信息交换,使用条件传输方式时可用“联络”线进行控制

可通过编程设置各端口工作方式和数据传送方向(入/出/双向)

并行数据宽度为8位

8255引线图

共40个引脚

连接至系统端的主要引线

注意,地址是端口ABC然后是控制寄存器,跟8253类似

连接外设端的引脚

PA0-PA7

PB0-PB7

PC0-PC7

对应了A, B, C三个8位输入/输出端口

三个端口可通过编程分别指定为输入或输出口

其中,C口即可用作独立的输入/输出口,也可用作A、B口的控制信号输出状态信号输入

8255内部结构

厂家除了把输入/输出接口电路集成在一块芯片中,还包括控制这些接口电路的控制部分,以及与CPU接口的总线接口部分


8255A芯片中包含3个8位端口:A口,B口,C口

这三个端口均可以作为CPU与外设通信时的缓冲器或锁存器

一般来说,它们做缓冲器使用时,是输入接口;作为锁存器使用时,是输出接口


C口可作为AB两端口的信号输入输出

条件传输方式需要“状态”或“联络”信号

中断传输方式需要“中断”信号

由于8255没有预先从芯片引脚上给出这些信号,因此在用户选择这两种工作方式时,8255A将从C口的8位I/O线中提取若干根线作为“状态”“联络”或“中断”线;在这种状态下,C口剩余的线依然可以作为I/O线

也就是说C口相当于“握手协议”,维持传输的稳定

3个端口通过各自的I/O线与外设联系

AB端口控制

8255A有3个端口,但不是每个端口都有自己独立的控制部件,只有两个控制部件

只有A组和B组有控制部件,这样3个端口就会分为2组

A组由A口和C口的高4位组成

B组由B口和C口的低4位组成

A组和B组有自己的控制部件,可同时接受来自读/写控制电路的命令和CPU送来的控制字,并且根据这些来定义各个端口的操作方式


注:虽然分了AB组,A组用C的高4位,但是后面方式1工作方式用的却不是高4位

方式1输入,A组用的是PC3,PC4,PC5,方式1输出,A组用的是PC3,PC6,PC7

数据总线缓冲器

数据总线缓冲器是CPU与8255A交换信息的必经之路

8255与系统的连接图

8255工作方式

总共3种工作方式

编号是0,1,2

方式0:基本输入/输出方式

方式0主要工作在无条件的输入/输出方式下,不需要“联络”信号

A口,B口,C口均可工作在此方式下,C口只能工作在方式0下

在这种方式下,CPU与端口之间交换数据可以直接由CPU执行IN或OUT指令完成,不需要检测状态线

由于在方式0下,3个端口可以分别定义为输入或输出端口,于是3个端口就会有下图16种输入/输出组合:

16的原因:C口可以分为两个4位的独立端口


8255在方式0相当于三个独立的8位简单接口

各端口既可设置为输入口,也可设置为输出口,但不能同时实现输入及输出

C端口即可以是一个8位的简单接口,也可以分为两个独立的4位端口

设置为输出口时有锁存能力,设置为输入口时无锁存能力


方式0的应用

用于连接简单外设

适用于:

无条件输入输出方式

查询输入输出方式:把A、B口作为8位数据的输入或输出口,C口的高/低4位分别定义为A、B口的控制位和状态位

方式1:选通输入/输出方式

方式1主要工作在异步或条件传输方式下

在方式1下,数据的输入、输出操作要在选通信号控制下完成,适合条件传输(必须先检查状态,然后才能传输数据)

仅有A口和B口可工作在此方式下,因为C口的部分位固定用作A、B口的选通控制信号(联络线),相当于握手协议

工作在方式1下的A口和B口可以作为输入接口,也可以作为输出接口

由于输入接口和输出接口所需要的选通控制不同,“联络线”的定义和功能也不同,需要分别介绍

选通输入方式

AB口均为输入

在这种方式下,A口或B口需有C口3根线作为联络线

PC3, PC4, PC5作为A口的联络线,PC0, PC1, PC2作为B口的联络线

信号功能

~STBA:A口的选通信号(外设发出,低电平有效),当其有效时,外设把数据输入A口的输入缓冲器

IBFA:A口的输入缓冲器“满”信号,当其有效时,表示A口的输入缓冲器已暂存一个有效数据,是输出信号;此信号由STB的前沿产生

INTRA:A口的中断请求信号,当其有效时,8255的A口向CPU申请中断,要求CPU从A口取数,是输出信号;此信号由STB的后沿产生

在这种方式下,A口的逻辑电路有一个INTEA信号,称为中断允许信号,这个信号为高时,与门导通,INTEA就可以作为INTRA中断信号输出了。INTEA对应C口输出锁存器的第4位,即通过对C口PC4的置位/复位就可以设置INTEA

即:INTEA =1和IBFA为高电平时,允许发出INTRA请求

发送数据过程

在方式1下,外设把一个数据通过A口送给CPU的过程如下:

1.外设把数据送到A口的数据线PA7-PA0后,使选通信号~STBA有效,数据进入A口的输入缓冲区

2.A口的IBFA有效,通知外设或CPU,表示A口接收了一个有效数据

3.A口的INTRA有效,以中断方式通知CPU取走A口的数据

4.CPU读A口,数据进入CPU

5.IBFA和INTRA转为无效

方式1数据输入的时序图如下:


可见,当A口接收外部数据以后,会有IBFA和INTRA的输出,对应了两种方式通知CPU取数:

条件查询方式,通过查询缓冲器是否“满”,即IBFA是否为高电平来取数

中断方式,先用控制字把C口的INTEA置位1,当IBFA=1时,这两个高电平通过与门产生高电平INTRA;若CPU允许中断,则中断当前执行的程序,转到对A口读数的中断服务子程序

握手信号

在条件传输中,一般需要有“握手”信号来协调数据的传输

“握手”信号至少要有两位信号线,其中一位由接口电路发给外设,另一位是外设发给接口

在选通方式中,~STBA和IBFA是一对“握手”信号

~STBA是外设发给接口,有效时通知A口,外设传递进来一个数据

IBFA是接口发给外设,有效时通知外设,A口已经接收这个数据

此时的状态字

从C口读出的8位数据各位的意义如下

选通输出方式

AB口均为输出

在这种方式下,A口或B口需有C口3根线作为联络线

PC3, PC6, PC7作为A口的联络线,PC0, PC1, PC2作为B口的联络线(与选通输入有点不同:A口的联络线变成了PC3, PC6, PC7)

信号功能

~OBFA:A口输出缓冲区“满”信号,有效时表示A口输出缓冲器已经暂存一个有效数据,通知外设取数据

~ACKA:外设应答信号,由外设发出,低电平有效时表示外设已经接收数据

INTRA:A口的中断请求信号,当其有效时,A口向CPU申请中断,要求CPU送数据给A口,由~ACKA上升沿产生(通常接到8259)

发送数据过程

在方式1输出方式下,CPU把数据通过A口送给外设的过程如下:

1.CPU执行OUT指令,把数据写入A口的输出缓冲器

2.当有效数据进入A口的数据线PA7-PA0时,~OBFA有效通知外设:CPU已把一个有效数据输出到A口,外设可以从A口取数据了

3.外设取走数据时,发~ACKA信号给8255,告诉A口外设已经取完数据

4.A口~OBFA无效,表示A口数据已经被外设取走

5.INTRA有效,以中断的方式通知CPU再输出数据给A口

方式1数据输出的时序图如下:


同样,当A口的输出缓冲器数据被外设取走后,有两种方式通知CPU再对A口写入数据:

条件查询方式,查询输出缓冲器是否为“空”,即~OBFA是否为高电平决定CPU是否转向对A口输出数据的程序

中断方式,先用控制字置A口的INTEA位为“1”,若外设取走A口的数据,应答信号~ ACKA有效,使~ OBFA为高电平;~ OBFA和INTEA都为高电平,通过与门产生INTRA中断信号,若CPU允许中断,则中断当前执行的程序,转到对A口写数的中断服务子程序

握手信号

在这种方式下,~ ACKA和~ OBFA是一对握手信号

~OBFA是接口发给外设,通知外设A口有了一个新数据

~ACKA是外设发给接口,通知接口外设已经把数据取走

此时状态字

方式1的应用

主要用于中断控制方式下的输入输出

C口除部分位用作选通信号外,其余位可工作在方式0下,作为输入或输出线

特别是A、B均为方式1时仅使用C口的6条线,余下二条线可作为单独的输入输出线,用程序指定其数据传送方向

方式2:双向传输方式(仅A口)

双向方式——既是输入口,又是输出口

利用C口的5条线提供传输联络信号

类似于A口方式1下输入和输出的组合

只有A口可工作在方式2下

8255控制字

控制字格式

8位控制字

有3种控制字:方式控制字,C口置1清0控制字,读入状态字

要记住所有寄存器

D7为1时,是选择工作方式控制字

D7为0时,是C口置位/复位控制字


方式控制字

控制字包括1位的功能控制,4位的A组控制,3位的B组控制

0输出,1输入

从高位到低位分别是:

D7功能控制,区分出来两种控制字:选择工作方式控制字C口置位/复位控制字

D7为1时,是选择工作方式控制字

D7为0时,是C口置位/复位控制字


D6, D5, D4, D3是A组控制

其中D6和D5选择工作方式(仅有A口能用方式2)

D4是A口的输入、输出选择

D3为C口高4位的输入、输出选择


D2, D1, D0是B组控制

D2为B口的方式选择

D1是B口的输入、输出选择

D0为C口低4位的输入、输出选择

C口置位/复位控制字

也就是可以直接操作C口的位

用D3,D2,D1指定PC0-PC7

然后再用D0选择这个口现在的信息

读入状态字

这个状态字是C口读出来的,而不是我们写进控制寄存器的

当8255A工作在方式1或方式2时,C口会根据不同情况产生或接收“联络”信号

8255作业

1.

利用8253(端口地址C0-C3H)作为计数器,8255(端口地址D0-D3H)的一个输出端口控制8个指示灯,请设计出电路连接图,并编写程序,使8个指示灯依次闪动:闪动频率为每秒1次。

答:

涉及到中断,就需要8259A,所以初始化也需要考虑8259

但是题目里没有明确指明8259的地址,和对应的中断类型号,所以就可以任取

比如:我规定8259地址为20H-21H,中断类型号为E0-E7H,中断用8259的IR0,边沿触发形式

设置接入8253的时钟信号为1KHZ


整体过程很类似“8253作业第一题”,所以此处并不会像8253第一题一样写的非常详细

整道题目的基本过程如下:

先分析硬件电路,基本思路就是8253时间到了往8259的IR0发一个信号,此处的信号为方波,那就是工作模式3

8259接到信号以后,像CPU产生一个中断

CPU响应中断服务子程序,直接向8255的A口发送数据,控制灯亮灭

用到8253的计数器0,8259的IR0,中断号设置成E0H,8255的A口


根据对应地址,让Y0被选中,8253的138译码电路为:

根据对应地址,让Y0被选中,8255的138译码电路为:

对应的硬件连接图为:

注意这里硬件连接图的灯,如果没有反相器,就是0亮1不亮,但是这里多了个反相器,就是逻辑非门,那就1亮0不亮

考试的时候自己爱咋连咋连,我如果自己画就不加这个反相器,但是我懒得画,直接用答案现成的就好


硬件电路分析完后,就是对应器件的初始化字了

先看8253:

选用计数器0,则开头两位为00

如果接入的CLK是1KHZ,我们需要让灯1秒切换一个状态,那就是重复的方波,工作模式3,则D3D2D1分别为011

对应计数值为:\(\dfrac{1}{\frac{1}{1\times10^3}}=1000\),对应的二进制就是0000_0011_1110_1000B,很明显比8位长,因此需要先低后高两次写入计数值,则D5D4为11

最后,不用BCD,则D0为0

所以8253对应的控制字就是0011_0110B

写对应的程序:

1
2
3
4
5
6
7
8
MOV	DX,	00C3H	;8253控制口
MOV AL, 0011_0110B
OUT DX, AL ;写控制字
MOV DX, 00C0H ;8253计数器0
MOV AL, 1110_1000B ;先写1000的低8位
OUT DX, AL
MOV AL, 0000_0011B ;再写1000的高8位
OUT DX, AL


再看8255:

这个更简单,只用到A口简单输出,只用到方式0,所以B口后面的所有位都可以不管

首先,设置工作方式,D7=1

其次,设置A口的工作方式为方式0,则D6D5为00

然后,设置A口为输出,D4为0

最后,因为C口和B口都用不到,所以D0D1D2D3都是0

所以8255的控制字为:1000_0000B

直接写入即可:

1
2
3
MOV	DX,	00D3H	;8255控制口
MOV AL, 1000_0000B
OUT DX, AL ;写8255控制字


最后看8259A:

ICW1:因为接入的是IR0,设置的是边沿触发,单片,写ICW4,则ICW1为0001_0011B

ICW2:设置IR0的中断向量码为E0H,则对应的ICW2为1110_0000B

ICW3:不用级联,不写

ICW4:最简单的中断结束方式,就写0000_0001B

OCW1:不需要中断屏蔽,不写

OCW2:不需要改变优先级,不写

OCW3:不需要中断屏蔽和读出,不写

最后对应8259的初始化程序:

1
2
3
4
5
6
7
8
MOV	DX,	0020H	;8259 A0=0
MOV AL, 0001_0011B
OUT DX, AL ;写ICW1
MOV DX, 0021H ;8259 A0=1
MOV AL, 1110_0000B
OUT DX, AL ;写ICW2
MOV AL, 0000_0001B
OUT DX, AL ;写ICW4

既然设置了中断,则需要有对应的中断服务程序

具体思路如下:在全局设置一个变量LED来控制8个灯亮与灭,0灭1亮,最开始就是0000_0001B

然后每过1秒,CPU响应一次中断程序

每次都可以把这个值左移一位,看起来就是灯在依次闪动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DATA	SEGMENT
LED DB 0000_0001B ;数据段,定义LED这个变量
DATA ENDS


MYINT PROC FAR
PUSHF ;保护现场
PUSH DX
PUSH AX
CLI ;关中断
MOV DX, D0H ;8255的A口
MOV AL, LED
OUT DX, AL ;把LED的值从A口输出到硬件
ROL LED ;控制LED循环左移,这样就可以保证依次亮
STI ;开中断
POP AX
POP DX
POPF
IRET ;记得中断返回
MYINT ENDP

我定义的中断服务程序名字叫MYINT,现在应该把这个程序放入IVT

具体过程很容易,保护DS等寄存器,关中断,把0放入DS,把中断类型号E0H乘4放入SI,把段偏移放入[SI],把段基址放入[SI+2],开中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CLI		;关中断
PUSH DS
PUSH AX
PUSH SI
MOV AX, 0000H
MOV DS, AX ;把0放入DS,准备写IVT
MOV SI, E0H * 4 ;E0H乘4存入SI
LEA AX, MYINT ;取偏移地址到AX
MOV [SI], AX ;偏移地址放入低字节
MOV AX, SEG MYINT ;取段基址
MOV [SI+2], AX ;段基址存入高字节
POP SI
POP AX
POP DS
STI ;开中断

2.

答:

(1)

有点逆天了,唯一的区别只有A15

当A15为0时,8253被选通,8255不起作用;当A15为1时,8255被选通,8253不起作用

因为地址线为16根,所以区别就在这里:

8253:0000_0000_0000_0000B0111_1111_1111_1111B,即0000H - 7FFFH

8255:1000_0000_0000_0000B1111_1111_1111_1111B,即8000H - FFFFH

但是我不明白,为什么端口会这么多

(2)

我指定:8253的起始地址为0000H,8255的起始地址为8000H

先看8253:

指定用方波,就是方式3,计数值为\(\dfrac{\frac{1}{100}}{\frac{1}{1\times10^6}}=1\times10^4\),写成二进制就是0010_0111_0001_0000B,注意别写错了,我感觉这个二进制转换还是挺容易错的

指定计数器1,所以最开头两位为01

因为10000的二进制超过8位,所以需要先低后高分2次存入,因此D5D4为11

工作方式为方式3,所以D3D2D1为011

没指定BCD,D0就是0

因此,8253控制字为0111_0110B

再看8255:

指定A输出,BC为输入,所以工作方式0

指定工作方式,第一位为1

A口为方式0,则D6D5为00

A口为输出,则D4为0

C口为输入,则D3和D0为1(D3控制高4位,D0控制低4位)

B口为方式0,则D2为0

B口为输入,则D1为1

对应的控制字为:1000_1011B

对应的初始化程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
;8253初始化
MOV DX, 0003H ;8253控制口
MOV AL, 0111_0110B
OUT DX, AL ;写8253控制字
MOV DX, 0001H ;8253计数器1
MOV AL, 0001_0000B
OUT DX, AL ;写10000的低八位
MOV AL, 0010_0111B
OUT DX, AL ;写10000的高八位

;8255初始化
MOV DX, 8003H ;8255控制口
MOV AL, 1000_1011B
OUT DX, AL ;写8255控制字

(3)

他这个拨码开关0-7最后的输入应该是独热码,即0000_0001, 0000_0010这样的,仅有1位是1,用这一位可以判断是哪个数

我不采用答案的写法,而是采用我在接口技术课设里电子琴的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
	;***************************************************************
; C大调音
;***************************************************************
START_C: TEST AL,2
JZ START_D
CALL Music_C ;1号键
JMP FAR PTR InitialState
START_D: TEST AL,4
JZ START_E
CALL Music_D ;2号键
JMP FAR PTR InitialState
START_E: TEST AL,8
JZ START_F
CALL Music_E ;3号键
JMP FAR PTR InitialState
START_F: TEST AL,10H
JZ START_G
CALL Music_F ;4号键
JMP FAR PTR InitialState
START_G: TEST AL,20H
JZ START_A
CALL Music_G ;5号键
JMP FAR PTR InitialState
START_A: TEST AL,40H
JZ START_B
CALL Music_A ;6号键
JMP FAR PTR InitialState
START_B:
CALL Music_B ;7号键
JMP FAR PTR InitialState

我的写法我认为很简单,就顺序判断0-7,执行对应的功能,最后跳转就可以了,可能写的有点长

开关如果置到1,输入B口的数据就是1111_1110B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
DATA	SEGMENT
BINARYCODE DB 01H, 02H, 03H,04H, 05H, 06H, 07H, 08H ;自定义的二进制编码
DATA ENDS

CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX ;DATA放入AX
CALL MYPRO ;调用函数

MYPRO PROC NEAR
PUSH DX
PUSH AX
PUSH SI ;保护
MOV DX, 8002H ;C口地址
READLOOP:
IN AX, DX ;读C口数据
TEST AX, 0000_0001B ;如果读出来的末位PC0是0,与的结果就是0,ZF就置1
JZ CONTINUE ;末位PC0是0,就跳转
JMP READLOOP ;如果末位不是0,就一直重复读
CONTINUE:
LEA SI, BINARYCODE ;表格地址地址放入SI
MOV DX, 8001H ;B口地址
IN AX, DX ;读B口数据到AX
CMP AX, 0000_0001B ;比较只会改变ZF的值,相当于做减法
JZ READ1 ;如果结果为0,说明两个相等,可以直接JZ跳转
CMP AX, 0000_0010B
JZ READ2
CMP AX, 0000_0100B
JZ READ3
CMP AX, 0000_1000B
JZ READ4
CMP AX, 0001_0000B
JZ READ5
CMP AX, 0010_0000B
JZ READ6
CMP AX, 0100_0000B
JZ READ7
CMP AX, 1000_0000B
JZ READ8
READ1:
MOV AL, [SI] ;对应二进制编码放入AL
JMP END ;跳转最后
READ2:
MOV AL, [SI+1] ;对应二进制编码放入AL
JMP END ;跳转最后
READ3:
MOV AL, [SI+2] ;对应二进制编码放入AL
JMP END ;跳转最后
READ4:
MOV AL, [SI+3] ;对应二进制编码放入AL
JMP END ;跳转最后
READ5:
MOV AL, [SI+4] ;对应二进制编码放入AL
JMP END ;跳转最后
READ6:
MOV AL, [SI+5] ;对应二进制编码放入AL
JMP END ;跳转最后
READ7:
MOV AL, [SI+6] ;对应二进制编码放入AL
JMP END ;跳转最后
READ8:
MOV AL, [SI+7] ;对应二进制编码放入AL
JMP END ;跳转最后
END:
MOV DX, 8000H ;A口地址
OUT DX, AL ;输出
POP SI
POP AX
POP DX
RET
MYPRO ENDP

CODE ENDS
END START

答案的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Disp	PROC	NEAR
PUSH CX
PUSH SI
XOR CX, CX ;CX置0
CLC
LEA SI, BINARYCODE ;获得存储编码位置的地址
MOV DX, 8002H ;PC口地址
GO:
IN AL, DX
CMP AL, 01H ;测试PC0是否为1
JNZ GO ;循环检测
MOV DX, 8001H ;读取PB口开关的值
IN AL, DX ;读取PB口开关的值
GO1:
SHR AL, 1 ;不停右移获得权重值
INC CX
JC GO1 ;标志位为1就转移
DEC CX ;多加的一次减掉
ADD SI, CX
MOV AL, [SI] ;读取编码值
MOV DX, 8000H ;赋给PA口
OUT DX, AL
POP SI
POP CX
RET
Disp ENDP

用循环和位移确实代码少一些

中断

中断的定义

CPU执行程序时,由于发生了某种随机的事件(外部或内部),引起CPU暂时中断正在运行的程序,转去执行一段特殊的服务程序(称为中断服务程序或中断处理程序),以处理该事件,该事件处理完后又返回被中断的程序继续执行,这一过程称为中断

中断源

引起CPU中断的事件就是中断源

中断源分为:外部中断、内部中断

外部中断

CPU以外的设备、部件产生的中断

内部中断

CPU内部执行程序时自身产生的中断

引入中断的原因

提高数据传输率

避免了CPU不断检测外设状态的过程,提高了CPU的利用率

实现对特殊事件的实时响应(如多任务系统操作系统中:缺页中断,设备中断,各类异常)

中断五个步骤

中断请求、中断判优(有时还要进行中断源识别)、中断响应、中断服务、中断返回

中断请求

外设接口(中断源)发出中断请求信号,送到CPU的INTR或NMI引脚

中断请求信号应保持到中断被处理为止

CPU响应中断后,中断请求信号应及时撤销

在8086/8088系统中,外设的中断要经过8259A可编程中断控制器(PIC)的排队判优后向CPU发出

中断判优

中断优先级控制要处理两种情况:

对同时产生的中断:应首先处理优先级别较高的中断;若优先级别相同,则按先来先服务的原则处理

对非同时产生的中断:低优先级别的中断处理程序允许被高优先级别的中断源所中断——即允许中断嵌套


中断优先级控制方法

分为硬件判优和软件判优

硬件判优

链式判优电路:

INTR为电平请求,只要有器件需要中断就过或门,发起中断请求

菊花链逻辑电路:

链式判优按照中断优先级的顺序,把硬件的中断顺序排好了

~INTA为CPU发出的响应信号

IREQ为连接到下面或门的中断请求信号,可以看看上面那张图

CPU的响应信号从优先级高的器件到优先级低的器件

如果高的器件没有IREQ信号,则菊花链中断确认的与非门就没有信号,~INTA响应信号通过或门继续到下一个器件

如果这个器件有IREQ信号,则~INTA信号先过一个非门,变成1;然后和IREQ信号(高电平)经过与非门输出低电平的中断确认信号,进入外设接口

同时,IREQ信号经过下面的或门,把INTAout信号置为1,后面的器件就没法获得低电平的相应信号了

软件判优

顺序查询中断请求,先查询的先服务(即先查询的优先级别高)

中断响应

CPU响应中断,要完成下面三个工作:

向中断源发出INTA中断响应信号

断点保护,包括CS、IP和PSW(FLAGS)。这主要是保证中断结束后能返回被中断的程序

获得中断服务程序首地址(入口)


想得到中断服务程序首地址,常用的是中断向量法

中断服务

中断服务子程序特点

  1. 为“远”过程(类型为FAR)

  2. 要用IRET指令返回


中断服务子程序要做的工作

  1. 保护现场(PUSH reg’s)

  2. 开中断(STI)

  3. 进行中断处理

  4. 恢复现场(POP reg’s)

  5. 中断返回(IRET)

中断返回

执行IRET指令

IRET指令将使CPU把堆栈内保存的断点信息弹出到IP、CS和FLAG中,保证被中断的程序从断点处能够继续往下执行

中断向量表(IVT)

存放各类中断的中断服务程序的入口地址(段和偏移)——中断向量

表的地址位于内存的00000H~003FFH,大小为1KB,共256个中断向量(一个中断向量占4个字节,表总长1024个字节,故\(1024\div4=256\)个中断向量)

每个中断向量占用4 Bytes低字为段内偏移,高字为段基址

根据中断类型号获得中断服务程序入口的方法: (n为中断类型号,从0开始) \[ 中断向量在IVT中的存放地址 = 4\times n \] 因为一个中断向量占4字节,所以乘4

中断向量表初始化

初始化 —— 将中断服务程序的入口地址放入向量表

两种方法:

  1. 直接将中断处理子程序入口地址填写进中断向量表中
  2. 通过调用DOS的25H子功能实现

中断类型码为48H的中断处理子程序的名字为int48h,编写程序段将该中断处理子程序的入口地址放入向量表

方法1:自己写

1
2
3
4
5
6
7
8
9
10
11
12
CLI			;清零IF,IF=0时表示CPU禁止中断,即关中断
PUSH DS ;保护DS的值,因为下面修改了DS的值
MOV AX, 0 ;0放入AX,因为中断向量表一般在内存的00000H-003FFH
MOV DS, AX ;DS现在存的就是0,即中断向量表的地址
MOV SI, 48H * 4 ;把48H在中断向量表中的偏移地址放入SI
;因为指明了中断类型码为48H,偏移地址就是48H * 4
MOV AX, OFFSET int48h ;把int48h的偏移地址放入AX,和LEA用法一致
MOV [SI], AX ;把偏移地址放入48H在中断向量表中该放的位置的低地址
MOV AX, SEG int48h ;把int48h的段地址放入AX,其中SEG int48h就是取int48h的段基址
MOV [SI+2], AX ;把段地址放入[SI]的高两个字节,因为低地址存的是偏移地址,高地址存的是段基址
POP DS ;弹出DS的值
STI ;IF置1,IF=1时CPU可响应中断,开中断

方法2:用DOS的25H调用

1
2
3
4
5
6
7
8
PUSH	DS
MOV AX, SEG int48h ;把int48h的段地址放入AX
MOV DS, AX ;把int48h的段地址放入DS
LEA DX, int48h ;把int48h的偏移地址放入DX
MOV AL, 48H ;把中断类型码放入AL
MOV AH, 25H ;调用DOS的25H,自动把中断向量写进去
INT 21H
POP DS

如果要用DOS的25H调用,首先得保护现在DS段的值

然后把中断处理子程序的段地址通过AX传给DS

再把中断处理子程序的偏移地址放入DX

中断类型码放入AL

就能调用DOS的25H了

相当于25H需要3个变量:中断处理子程序的段地址(放入DS)、中断处理子程序的偏移地址(放入DX)、中断处理子程序的中断类型码(放入AL)

8086中断响应过程

这个算是重点?我看作业有这个考点

主要步骤:

  1. PUSH FLAG
  2. IF = 0,即关中断
  3. PUSH CS
  4. PUSH IP
  5. 取中断向量送入IP和CS


例题

比如一个作业题:

已知(SP)=0100H,(SS)=3500H,(CS)=9000H,(IP)=0200H,(00020H)=7FH,(00021H)=1AH,(00022H)=07H,(00023H)=6CH

在地址为90200H开始的连续2个单元中存放1条两字节指令INT 8

试指出在执行该指令并进入相应的中断程序时,SP、SS、IP、CS寄存器的内容以及SP所指向的字单元的内容是什么?

答:

执行该指令时,IP为下一条指令的地址,即0200H+2H=0202H

8086响应中断,首先把FLAG压栈,所以SP先减2为00FEH

再关中断,这个不影响这些寄存器

再把CS内容压栈,即SP再减2为00FCH

再把IP内容压栈,即SP再减2为00FAH,此时SP指向的内容就是0202H

这时就取中断向量送入IP和CS了

把偏移地址送入IP,把段基址送入CS

于是,答案为:

SS不变,3500H

SP压栈3次,变为00FAH

IP变为1A7FH

CS变为6C07H

SP指向的内容,看最后一个被压栈的内容:IP,即0202H

8086系统各中断优先级

优先级从高到低顺序如下:

内部中断

NMI(非屏蔽中断)

INTR(可屏蔽中断)

单步中断

8259A可编程中断控制器

1片可对8个中断源实现优先级控制,通过扩展可以对64个中断源实现优先级控制

可编程设置IR0-IR7中断优先级次序

可编程设置IR0-IR7中断类型码

可编程对IR0-IR7进行中断屏蔽设置

可编程读出内部IRR、ISR、IMR寄存器值,以了解中断情况

可编程设置8259A的工作方式

8259A引脚

8259A内部结构

需要记的寄存器如下:

IRR(Interrupt Request Register)

ISR(Interrupt Service Register)

IMR(Interrupt Mute Register)

中断请求寄存器IRR

保存从IR0~IR7来的中断请求信号,某位=1表示对应的 IR_i 有中断请求

中断服务寄存器ISR

保存所有正在服务的中断源,某位=1表示对应的 IR_i 中断正在被服务

中断屏蔽寄存器IMR

存放中断屏蔽字,某位=1表示对应的 IR_i 输入被屏蔽

中断优先权判别电路

确定是否向CPU发出中断请求,中断响应时确定ISR的哪位应置位及把相应中断的类型码放到数据总线上

8259A工作过程

当某 IR_i 有效时,IRR相应位置1

若有效的 IR_i 未被屏蔽,则向CPU发出中断请求

检测到第1个INTA#信号后,置ISR_i=1,IRR i=0(及时清空)

检测到第2个INTA#信号后,把ISR_i=1中最高优先级的中断类型码放到DB上

若工作在AEOI方式,在第2个INTA#结束时,使ISRi复位;否则由CPU发出EOI命令使ISRi复位

8259A工作方式

中断优先方式与中断嵌套

中断结束处理方式

屏蔽中断源的方式

中断触发方式

级联工作方式


8259A工作方式设置:通过编写初始化程序实现

对其内部的ICW1-4四个命令寄存器进行设置和编写控制程序

对3个操作控制寄存器OCW0-2设置

中断优先方式

中断优先方式 :通过OCW2来设置

两种优先级控制方式:固定优先级循环优先级

固定优先级方式——OCW2中R位(D7位)= 0

循环优先级方式——OCW2中R位(D7位)= 1

固定优先级

所有中断请求IRi的中断优先级固定不变

优先级排列顺序可编程改变

加电后8259A的默认方式,默认优先级顺序从高到低为IR0~IR7

循环优先级

中断源轮流处于最高优先级,即自动中断优先级循环

初始优先级顺序可用编程改变

某中断请求IRi被处理后,其优先级别自动降为最低,原来比它低一级的中断上升为最高级

如图,此时有2个中断请求:IR6和IR4,IR4优先级更高,所以先响应IR4

响应完IR4以后,IR4的优先级就变成7,也就是最低优先级,最高优先级变成了IR5

优先级调换是在响应完一个中断请求后

中断嵌套方式

中断嵌套方式—ICW4中D4位(SFNM)

在中断处理过程中允许被更高优先级的事件所中断称为中断嵌套

有两种中断嵌套方式:

一般全嵌套方式(默认方式): ICW4的 SFNM=0

一中断正被处理时,只有更高优先级的事件可以打断当前的中断处理过程而被服务

特殊全嵌套方式 : ICW4的 SFNM=1

一中断正被处理时,允许同级或更高优先级的事件可以打断当前的中断处理过程而被服务

中断结束处理方式

当某一IRi中断被服务时,ISR中的相应位ISRi=1

当服务结束后,则必须清零该ISRi位

使ISRi=0是通过向8259A发出中断结束命令EOI命令)实现的

三种EOI命令:ICW4的D1位AEOI

AEOI=1:自动EOI(AEOI)——(自动EOI方式)

非指定EOI(NSEOI)——(正常EOI方式)

指定EOI(SEOI)——(特殊EOI方式)


屏蔽中断源的方式

IMR屏蔽字决定了允许或禁止某位IRi所对应的中断:IMi=1 禁止, IMi=0 允许

特殊屏蔽方式:通过OCW3的D6D5位设置

中断触发方式

边沿触发

IRi出现上升沿表示有中断请求

电平触发

IRi出现高电平表示有中断请求

在第1个~INTA结束前,IRi必须保持高电平

级联工作方式

单片8259A可支持8个中断源

采用多片8259A级连,可最多支持64个中断源

n片8259A可支持7n+1个中断源

级连时只能有一片8259A为主片,其余的均为从属片


涉及到的8259A引脚包括:

CAS0-CAS2(连接级联缓冲比较器)

SP#/EN#

IRi

INT

可以看到,主片的INT作为整个级联的INT

从片的INT当做主片的IRi

主片的CAS0-CAS2依次连接到从片的CAS0-CAS2

8259A的编程使用

8259的控制命令分为:

初始化命令字ICW(ICW1-4)

操作命令字OCW(OCW1-3)

8259有2个地址:

A0=0时写ICW1, OCW2, OCW3

A0=1时写ICW2, ICW3, ICW4, OCW1


向8259A写入ICW的过程称为初始化编程

初始化的任务:是设置中断请求信号的有效形式,确定是单片工作还是级联工作(ICW1);设定IR0—IR7的中断类型号(ICW2);设定8259A的级连情况(ICW3);设定IR0—IR7各级请求信号的优先排序规则和中断结束操作规则(中断结束时如何清除ISR的中断标志位)(ICW4)


向8259A写入OCW的过程称为操作方式编程

写ICW

8259A初始化顺序

ICW1——初始化字

初始化8259A必须从ICW1开始

写ICW1意味着重新初始化8259A

写入ICW1后,8259A的状态如下:

清除ISR(中断服务寄存器)和IMR(中断屏蔽寄存器,全0,1为屏蔽,0 不屏蔽)

将中断优先级设成初始状态:IR0最高,IR7最低

设定为一般屏蔽方式

采用非自动EOI中断结束方式

状态读出逻辑预置为读IRR

设置中断请求信号的有效形式,确定是单片工作还是级联工作

高3位没用,指定D4为1

ICW2——中断向量码

也就是说,前5位是指定好的,后面3位会根据中断源0-7自行设置

比如:

如果\(IR_0\)的中断向量码是:0100_1000B(48H)

\(IR_1\)的中断向量码是:0100_1001B(49H)

\(IR_2\)的中断向量码是:0100_1010B(4AH)

...

\(IR_7\)的中断向量码是:0100_1111B(4FH)

ICW3——级联控制字(连在哪)

1用0不用

级联,就分为主片和从片

主片的ICW3控制字很容易,哪连了线,哪一位就是1

从片的ICW3控制字,前面5位定死为0,仅后面3位可变,说明了本从片连接到主片的哪一个引脚,IR0-7

主片从片对应关系必须一致

比如主片IR4与从片INT连接,主片的ICW3就是0001_0000B(10H),从片的ICW3就是0000_0100B(前5位定死为0,04H)

CAS三根线的作用就体现在这里,用来给从片确认是否能发送中断向量

ICW4——中断结束方式字

高三位定死为0,最低位定死为1

写OCW

OCW用于设置8259的工作状态:改变8259A的中断控制方式、屏蔽某几个中断源、控制读出IRR、ISR、IMR的值,以了解中断情况信息

在初始化后任意时刻写入

OCW的写入顺序可任意

OCW1——中断屏蔽字

1屏蔽,0允许

OCW1对应的A0应该为1

OCW2——中断结束和优先级循环

中断优先方式 :通过OCW2来设置

两种优先级控制方式:固定优先级循环优先级

固定优先级方式——OCW2中R位(D7位)= 0

循环优先级方式——OCW2中R位(D7位)= 1

固定优先级

所有中断请求IRi的中断优先级固定不变

优先级排列顺序可编程改变

加电后8259A的默认方式,默认优先级顺序从高到低为IR0~IR7

循环优先级

中断源轮流处于最高优先级,即自动中断优先级循环

初始优先级顺序可用编程改变

某中断请求IRi被处理后,其优先级别自动降为最低,原来比它低一级的中断上升为最高级

如图,此时有2个中断请求:IR6和IR4,IR4优先级更高,所以先响应IR4

响应完IR4以后,IR4的优先级就变成7,也就是最低优先级,最高优先级变成了IR5

优先级调换是在响应完一个中断请求后

OCW3——屏蔽方式和读出控制字

8259A编程举例

上升沿,单片,ICW4,这个是写ICW1,高3位没用,指定D4为1,D2没用

D3写LTIM,0为上升沿,1为高电平

D1写单片还是级联,1单片,0级联

D0写ICW4,0不写1写

对应就是0001_0010B(12H)


对应中断向量码给定,用于写ICW2

ICW2,后面3位随着IR0到IR7变,000到111

所以告诉你IR0对应08H,就是0000_1000B

故ICW2就写0000_1000B,就是IR0对应的中断向量码


IR4到IR7不使用,则修改屏蔽字OCW1:1屏蔽,0允许

即OCW1=1111_0000B(F0H)

1
2
3
4
5
6
7
8
9
INIT8259A:
MOV DX, 20H ;A0=0,写ICW1
MOV AL, 12H ;上升沿触发,单片,不写ICW4
OUT DX, AL
MOV DX, 21H ;A0=1,写ICW2,OCW1
MOV AL, 08H ;ICW2
OUT DX, AL
MOV AL, 0F0H;OCW1:屏蔽IR4-IR7
OUT DX, AL

中断程序设计

⑴ 确定要使用的中断类型号

某些中断类型号已被系统占用,用户只能占用那些专门保留给用户的中断类型号(PC机可供用户使用的中断类型号为:60H-66H和68H-6FH)

⑵ 保存原中断向量

可使用DOS的35H子功能完成(取出中断向量保存在ES:BX中)

⑶ 设置自己的中断向量

可使用DOS的25H子功能完成

⑷ 设置中断屏蔽字(可选)

通过8259A的OCW1设置完成

⑸ CPU开中断

8086CPU通过STI指令修改标志寄存器IF=1实现

⑹ 退出前要恢复原中断向量

可使用DOS的25H子功能完成

8259作业

1.

某输入接口(74LS244)的端口地址为0E54H,输出接口(74LS273)的端口地址为01FBH。试编写程序,使当输入接口的1、4、7位同时为1时,CPU将内存中DATA为首地址的20个单元的数据从输出接口输出;若不满足上述条件则等待。

答:

判断D1D4D7同时为1:先把原数和1001_0010B相与,得到的结果只会保留这三位

再与1001_0010B做减法,如果为0,就成立了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MOV	CX,	20	;循环次数放入CX
LEA BX, DATA ;数据起始地址放入BX
MYLOOP:
MOV DX, 0E54H ;输入端口
MYWAIT:
IN AL, DX ;获取输入
AND AL, 1001_0010B ;相与,保留147位
CMP AL, 1001_0010B ;相减,如果结果为0就说明题目要求的成立
JNZ MYWAIT ;如果结果不为0,就一直循环MYWAIT
MOV DX, 01FBH ;输出端口
MOV AX, [BX] ;获取BX数据
OUT DX, AX ;输出数据
INC BX
LOOP MYLOOP ;循环20次

其实我感觉这样写也行?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MOV	CX,	20	;循环次数放入CX
LEA BX, DATA ;数据起始地址放入BX
MOV DX, 0E54H ;输入端口
MYWAIT:
IN AL, DX ;获取输入
AND AL, 1001_0010B ;相与,保留147位
CMP AL, 1001_0010B ;相减,如果结果为0就说明题目要求的成立
JNZ MYWAIT ;如果结果不为0,就一直循环MYWAIT
MOV DX, 01FBH ;输出端口
MYLOOP: ;这种写法,就是在147都为1成立以后,一口气传完20位
MOV AX, [BX] ;获取BX数据
OUT DX, AX ;输出数据
INC BX
LOOP MYLOOP ;循环20次

这种写法,就是在147都为1成立以后,一口气传完20位

因为题目没细说,所以逻辑上来说应该是都行

2.

PC机8259A中断控制器地址为20H、21H,中断类型号为08H-0FH

初始已经设置为“边沿触发”、“普通结束方式”,外中断源连接到8259A的IRQ7上,响应中断时显示一个字符串

系统还通过开关,决定响应2个软中断(类型号60H-66H),其中1个软中断要求通过发光二极管显示其对应中断类型号,另一个软中断通过发光二极管显示当前开关的值

要求画出电路图,并编写程序。

答:

这道题并不难,先分析要求:

先是8259与一个中断源连接,如果中断源产生中断信号,8259就要给CPU产生一个中断信号,来响应8259对应的中断服务子程序:显示一串字符串,这个用DOS的9H就能实现,把字符串首地址放入DX就行,跟显示单个字符很类似


然后题目还要求实现两个软中断,这个通过开关输入实现,把信息输出到二极管

而开关和二极管就是输入输出,说到底也有个地址,根据对应的地址读取输入,信息输出就可以了

至于这个软中断,仍然是正常的中断子程序,只是调用方式用INT来调用,子程序退出仍然是用IRET,仍然要把这个程序放入IVT

软中断的调用条件,就可以写一个死循环,然后每次判断开关的值,调用对应的软中断即可


硬件连线:

我规定输入的开关地址为00F1H,输出的二极管地址为00F0H

连接电路如下:


程序结构分析完成,现在开始实际作答

8259的初始化字:

ICW1:边沿触发,D3为0;单片8259,D1为1;普通结束方式,写ICW4为0000_0001B,所以ICW1的D0为1

故ICW1为0001_0011B

ICW2:题目规定了中断类型号为08H-0FH,则ICW2为0000_1000B

ICW3:没有级联,不需要

ICW4:普通结束,ICW4为0000_0001B

OCW123不需要,不写

在8259初始化的同时也要把对应的中断子程序放入IVT,我设置这个程序名为PRINT,直接用就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
INIT8259	PROC	NEAR
MOV DX, 20H ;A0=0
MOV AL, 0001_0011B
OUT DX, AL ;写ICW1
MOV DX, 21H ;A0=1
MOV AL, 0000_1000B
OUT DX, AL ;写ICW2
MOV AL, 0000_0001B
OUT DX, AL ;写ICW4

CLI ;关中断
PUSHF
PUSH DS
PUSH AX
PUSH SI
MOV AX, 0
MOV DS, AX ;0放入数据段,写IVT
MOV SI, 0FH * 4 ;中断类型号*4放入SI
;注意,这里接入的是IR7,对应的中断类型号为0FH
LEA AX, PRINT ;中断子程序的偏移地址放入AX
MOV [SI], AX ;偏移地址写入低字节
MOV AX, SEG PRINT ;段基址放入AX
MOV [SI+2], AX ;段基址写入高字节
POP SI
POP AX
POP DS
POPF
STI ;开中断

RET
INIT8259 ENDP

写完8259初始化程序, 接下来就接着写对应的PRINT中断子程序

我设置数据段里面存STR为要显示的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DATA	SEGMENT
STR DB 'STRING'
DATE ENDS


PRINT PROC NEAR
PUSHF
PUSH AH
PUSH DX
CLI
LEA DX, STR ;从DATA取数组,基本都是LEA
MOV AH, 9H
INT 21H
STI
POP DX
POP AH
POPF
IRET
PRINT ENDP

写完8259,就准备写输入输出控制的软中断了


我规定输入的开关地址为00F1H,输出的二极管地址为00F0H

我再规定,把软中断程序写入IVT中断向量表的程序名叫SOFT1SOFT2,软中断对应的两个中断程序名叫INT1INT2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
SOFT1	PROC	NEAR		;把软中断1写入IVT的程序
CLI ;关中断
PUSHF
PUSH DS
PUSH AX
PUSH SI
MOV AX, 0
MOV DS, AX ;0放入数据段,写IVT
MOV SI, 60H * 4 ;中断类型号60H*4放入SI
LEA AX, INT1 ;中断子程序的偏移地址放入AX
MOV [SI], AX ;偏移地址写入低字节
MOV AX, SEG INT1 ;段基址放入AX
MOV [SI+2], AX ;段基址写入高字节
POP SI
POP AX
POP DS
POPF
STI ;开中断
SOFT1 ENDP

SOFT2 PROC NEAR ;把软中断2写入IVT的程序
CLI ;关中断
PUSHF
PUSH DS
PUSH AX
PUSH SI
MOV AX, 0
MOV DS, AX ;0放入数据段,写IVT
MOV SI, 61H * 4 ;中断类型号61H*4放入SI
LEA AX, INT2 ;中断子程序的偏移地址放入AX
MOV [SI], AX ;偏移地址写入低字节
MOV AX, SEG INT2 ;段基址放入AX
MOV [SI+2], AX ;段基址写入高字节
POP SI
POP AX
POP DS
POPF
STI ;开中断
SOFT2 ENDP

这两个就是对上面的复制粘贴,只需要改对应的程序名和中断向量号


现在写对应的软中断程序1:通过发光二极管显示其对应中断类型号

就是把60H输出到二极管即可,很容易

1
2
3
4
5
6
7
8
9
10
INT1	PROC	NEAR
PUSHF
CLI
MOV DX, 00F0H ;输出,二极管的地址
MOV AL, 60H
OUT DX, AL ;把60H输出到二极管
STI
POPF
IRET ;中断退出
INT1 ENDP


现在写对应的软中断程序2:通过发光二极管显示当前开关的值

也就是说,先读输入,再把这个输入值,输出到二极管,也很简单

1
2
3
4
5
6
7
8
9
10
11
INT2	PROC	NEAR
PUSHF
CLI
MOV DX, 00F1H ;输入开关的地址
IN AL, DX ;输入读到AL
MOV DX, 00F0H ;输出,二极管的地址
OUT DX, AL ;把读取的输入直接输出到二极管
STI
POPF
IRET ;中断退出
INT2 ENDP


最后就是总体的程序,把上面所有进行结合即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
DATA	SEGMENT	
STR DB 'STRING'
DATE ENDS


CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
CALL INIT8259
CALL SOFT1
CALL SOFT2

;此处开始是新的,一直读取输入,判断调用哪个软中断
LOOP:
MOV DX, 00F1H ;输入地址
IN AL, DX ;读输入到AL
CMP AL, 01H ;判断用INT1
JZ CALLINT1
CMP AL, 02H ;判断用INT2
JZ CALLINT2
JMP LOOP ;死循环,一直反复执行
CALLINT1:
INT 60H ;调用中断子程序INT1
JMP LOOP
CALLINT2:
INT 61H ;调用中断子程序INT2
JMP LOOP


INIT8259 PROC NEAR
MOV DX, 20H ;A0=0
MOV AL, 0001_0011B
OUT DX, AL ;写ICW1
MOV DX, 21H ;A0=1
MOV AL, 0000_1000B
OUT DX, AL ;写ICW2
MOV AL, 0000_0001B
OUT DX, AL ;写ICW4

CLI ;关中断
PUSHF
PUSH DS
PUSH AX
PUSH SI
MOV AX, 0
MOV DS, AX ;0放入数据段,写IVT
MOV SI, 0FH * 4 ;中断类型号*4放入SI
;注意,这里接入的是IR7,对应的中断类型号为0FH
LEA AX, PRINT ;中断子程序的偏移地址放入AX
MOV [SI], AX ;偏移地址写入低字节
MOV AX, SEG PRINT ;段基址放入AX
MOV [SI+2], AX ;段基址写入高字节
POP SI
POP AX
POP DS
POPF
STI ;开中断

RET
INIT8259 ENDP

SOFT1 PROC NEAR ;把软中断1写入IVT的程序
CLI ;关中断
PUSHF
PUSH DS
PUSH AX
PUSH SI
MOV AX, 0
MOV DS, AX ;0放入数据段,写IVT
MOV SI, 60H * 4 ;中断类型号60H*4放入SI
LEA AX, INT1 ;中断子程序的偏移地址放入AX
MOV [SI], AX ;偏移地址写入低字节
MOV AX, SEG INT1 ;段基址放入AX
MOV [SI+2], AX ;段基址写入高字节
POP SI
POP AX
POP DS
POPF
STI ;开中断
SOFT1 ENDP

SOFT2 PROC NEAR ;把软中断2写入IVT的程序
CLI ;关中断
PUSHF
PUSH DS
PUSH AX
PUSH SI
MOV AX, 0
MOV DS, AX ;0放入数据段,写IVT
MOV SI, 61H * 4 ;中断类型号61H*4放入SI
LEA AX, INT2 ;中断子程序的偏移地址放入AX
MOV [SI], AX ;偏移地址写入低字节
MOV AX, SEG INT2 ;段基址放入AX
MOV [SI+2], AX ;段基址写入高字节
POP SI
POP AX
POP DS
POPF
STI ;开中断
SOFT2 ENDP

INT1 PROC NEAR
PUSHF
CLI
MOV DX, 00F0H ;输出,二极管的地址
MOV AL, 60H
OUT DX, AL ;把60H输出到二极管
STI
POPF
IRET ;中断退出
INT1 ENDP

INT2 PROC NEAR
PUSHF
CLI
MOV DX, 00F1H ;输入开关的地址
IN AL, DX ;输入读到AL
MOV DX, 00F0H ;输出,二极管的地址
OUT DX, AL ;把读取的输入直接输出到二极管
STI
POPF
IRET ;中断退出
INT2 ENDP



CODE ENDS
END START

3.

已知(SP)=0100H,(SS)=3500H,(CS)=9000H,(IP)=0200H,(00020H)=7FH,(00021H)=1AH,(00022H)=07H,(00023H)=6CH

在地址为90200H开始的连续2个单元中存放1条两字节指令INT 8

试指出在执行该指令并进入相应的中断程序时,SP、SS、IP、CS寄存器的内容以及SP所指向的字单元的内容是什么?

答:

执行该指令时,IP为下一条指令的地址,即0200H+2H=0202H

8086响应中断,首先把FLAG压栈,所以SP先减2为00FEH

再关中断,这个不影响这些寄存器

再把CS内容压栈,即SP再减2为00FCH

再把IP内容压栈,即SP再减2为00FAH,此时SP指向的内容就是0202H

这时就取中断向量送入IP和CS了

把偏移地址送入IP,把段基址送入CS

于是,答案为:

SS不变,3500H

SP压栈3次,变为00FAH

IP变为1A7FH

CS变为6C07H

SP指向的内容,看最后一个被压栈的内容:IP,即0202H

4.

某系统中设置三片8259A,级连使用,一片为主8259A(端口地址为20H、21H),2片为从8259A(端口地址分别为A0H、A1H和B0H、B1H)它们分别接入主8259A的IR2和IR0端。若已知当前主8259A和从8259A的IR3上各接有一个外中断源,它们的中断类型码分别为C3H,E3H,F3H,已知它们的中断入口均在同一段中,其段地址为2050H,偏移地址分别为11A0H、22B0H、33C0H,所有中断都采用电平触发方式、完全嵌套、普通EOI结束方式。要求:

⑴ 画出它们的硬件连接图;

⑵ 编写全部的初始化程序。

答:

(1)

电路图有些麻烦,因为涉及到8259级联

级联,就是把从片的INT连接到主片的IR上,题目要求,一个连IR2,一个连IR0;并且把主片的CAS信号引出来,分别连到从片对应的CAS上,很容易

然后就是,题目说的,三个8259的IR3都连接外部中断,连上就行

主片的地址为:0010_0000B0010_0001B

从片1的地址为:1010_0000B1010_0001B

从片2的地址为:1011_0000B1011_0001B

利用差别就可以唯一选中这些芯片,还是建议用138芯片的Y0输出

(2)

分析各个芯片的控制字

主片控制字:

ICW1:电平触发,D3为1;有级联,所以D1为0;要普通EOI结束,需要写ICW4,D0为1

所以ICW1为0001_1001B

ICW2:对应的中断向量号为C0H,因为IR3对应的是C3H,这里要求的是IR0的中断向量号,对应为1100_0000B

ICW3:用到级联,需要写:对应位1用0不用

主片用到的从片的位有IR0, IR2,没有 IR3,因为IR3是连的外界中断源,所以对应的ICW3为0000_0101B

ICW4:正常EOI就是0000_0001B,很好记

OCW1:不屏蔽,不写

OCW2:完全嵌套,就是0010_0000B

OCW3:不需要,不写


连接在主片IR2上的从片的控制字:

ICW1:电平触发,D3为1;有级联,所以D1为0;要普通EOI结束,需要写ICW4,D0为1

所以ICW1为0001_1001B

ICW2:对应的中断向量号为E0H,因为IR3对应的是E3H,这里要求的是IR0的中断向量号,对应为1110_0000B

ICW3:连接在主片的IR2上,所以ICW3为0000_0010B

ICW4:正常EOI就是0000_0001B,很好记

OCW1:不屏蔽不写

OCW2:和主片一样,完全嵌套,就是0010_0000B

OCW3:不需要,不写


连接在主片IR0上的从片的控制字:

ICW1:电平触发,D3为1;有级联,所以D1为0;要普通EOI结束,需要写ICW4,D0为1

所以ICW1为0001_1001B

ICW2:对应的中断向量号为F0H,因为IR3对应的是F3H,这里要求的是IR0的中断向量号,对应为1111_0000B

ICW3:连接在主片的IR0上,所以ICW3为0000_0000B

ICW4:正常EOI就是0000_0001B,很好记

OCW1:不屏蔽不写

OCW2:和主片一样,完全嵌套,就是0010_0000B

OCW3:不需要,不写


最后的最后,编写初始化程序

初始化要考虑控制字的写,还要考虑中断向量放入IVT

主片初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
;主片8259初始化
MOV DX, 20H ;A0=0
MOV AL, 0001_1001B
OUT DX, AL ;写ICW1
MOV DX, 21H
MOV AL, 1100_0000B
OUT DX, AL ;写ICW2
MOV AL, 0000_0101B
OUT DX, AL ;写ICW3
MOV AL, 0000_0001B
OUT DX, AL ;写ICW4
MOV DX, 20H ;A0=0
MOV AL, 0010_0000B
OUT DX, AL ;写OCW2
;把IR3对应的中断服务程序写入IVT
PUSHF
CLI
MOV AX, 0
MOV DS, AX
MOV SI, C3H * 4 ;中断向量号乘4
MOV AX, 11A0H ;偏移地址
MOV [SI], AX ;偏移地址放入低字节
MOV AX, 2050H ;段基址
MOV [SI+2], AX ;段基址放入高字节
STI
POPF

连接主片IR2的从片初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
;连接主片IR2的从片初始化
MOV DX, 20H ;A0=0
MOV AL, 0001_1001B
OUT DX, AL ;写ICW1
MOV DX, 21H
MOV AL, 1110_0000B
OUT DX, AL ;写ICW2
MOV AL, 0000_0010B
OUT DX, AL ;写ICW3
MOV AL, 0000_0001B
OUT DX, AL ;写ICW4
MOV DX, 20H ;A0=0
MOV AL, 0010_0000B
OUT DX, AL ;写OCW2
;把IR3对应的中断服务程序写入IVT
PUSHF
CLI
MOV AX, 0
MOV DS, AX
MOV SI, E3H * 4 ;中断向量号乘4
MOV AX, 22B0H ;偏移地址
MOV [SI], AX ;偏移地址放入低字节
MOV AX, 2050H ;段基址
MOV [SI+2], AX ;段基址放入高字节
STI
POPF

连接主片IR0的从片初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
;连接主片IR0的从片初始化
MOV DX, 20H ;A0=0
MOV AL, 0001_1001B
OUT DX, AL ;写ICW1
MOV DX, 21H
MOV AL, 1111_0000B
OUT DX, AL ;写ICW2
MOV AL, 0000_0000B
OUT DX, AL ;写ICW3
MOV AL, 0000_0001B
OUT DX, AL ;写ICW4
MOV DX, 20H ;A0=0
MOV AL, 0010_0000B
OUT DX, AL ;写OCW2
;把IR3对应的中断服务程序写入IVT
PUSHF
CLI
MOV AX, 0
MOV DS, AX
MOV SI, F3H * 4 ;中断向量号乘4
MOV AX, 33C0H ;偏移地址
MOV [SI], AX ;偏移地址放入低字节
MOV AX, 2050H ;段基址
MOV [SI+2], AX ;段基址放入高字节
STI
POPF





参考资料

课程PPT