微机原理之汇编语言程序设计
汇编语言程序设计入门(学习小甲鱼系列教学视频笔记)
文章目录
汇编语言程序设计
2024.8.16 更新。拖了好久,终于搞了图床,有图片了
1 基础知识
1.1 总线
连接CPU和其他芯片的导线
- 地址总线
- 数据总线
- 控制总线
内部总线实现CPU内部各个器件之间的联系
外部总线实现CPU和主板上其他器件的联系
小结
- 一个存储单元可以存储8个bit,即8位二进制数,一个字节
1.2 各类存储器芯片
从读写属性上分
-
随机存储器(RAM)
- 断电数据会丢失
-
只读存储器(ROM)
2 CPU工作原理
2.1 通用寄存器
- 一个16位寄存器所能存储的数据最大值是 $ 2^{16}-1$
- 从零开始,第16位权重为 2 16 2^{16} 216
2.2 几条汇编指令
;AX = 00C5H
ADD AL,85H ;运算结果为158H,但AL不会进位到AH,所以AX = 0058H
2.3 逻辑地址
- 偏移地址为16位,16位地址的寻址能力为64K,所以一个段的长度最大为64KB
- CPU访问内存单元时必须向内存提供内存单元的物理地址
- 一个地址指向一个内存单元,一个内存单元等于一个字节,一个字节等于八位
2.4 CS和IP
- CS为代码段寄存器
- IP为指令指针寄存器
他们指示了CPU当前要读取指令的地址
工作过程
(1)从CS:IP指向内存单元读取指令,读取指令进入缓冲器
(2)IP = IP + 所读取的指令的长度,从而指向下一条指令
(3)执行指令。转到步骤(1),重复。
8086CPU上电启动或复位后,CS:IP被设置为FFFF:0000,即刚启动时,FFFF0H单元中的指令是开机执行的第一条指令
如何修改CS,IP的值?
CS,IP的值不能用 MOV 来修改,要用 MOV
;JMP 段地址:偏移地址
JMP 2AE3:3
JMP 3:0B16
;JMP同时修改CS,IP的值
;仅修改IP的内容
;JMP 某一合法寄存器
JMP AX ;类似于 MOV IP,AX
JMP BX
2.5 DEBUG
-
R命令查看、改变CPU寄存器的内容
R AX
-
D命令查看内存中的内容
-
E命令改写内存中的内容
-
U命令将内存中的机器指令翻译成汇编指令
-
T命令执行一条机器指令
-
A命令以汇编指令的格式在内存中写入一条机器指令
-
Q命令是退出
-
G加上偏移地址可以使程序直接跳到代码位置
-
P跳过循环
-
Debug将程序从可执行文件加载入内存后,CX中存放的是程序的长度
-
Debug执行到
INT 21
是要用P命令执行结束程序
3 内存访问
3.1 内存中的字存储
(1) 2地址单元存放的字型数据是多少?
0012H
(2) 2地址存放的字节型数据是多少?
12H
(3) 1地址单元存放的字型数据是多少?
124EH
3.2 DS和[address]
- CPU要读取一个内存单元时,必须先给出这个内存单元的地址
- 8086CPU中有一个DS寄存器,通常用来存放
要访问的数据
的段地址
例,读取10000H单元的内容可用如下程序段进行
MOV BX,1000H
MOV DS,BX
MOV AL,[0] ;[]中放的是偏移地址,段地址由DS提供
上面三条指令将1000:0中的数据读到AL中
- 8086CPU不支持将数据直接送入
段寄存器
的操作,只能经过通用寄存器
- 数据→通用寄存器→段寄存器
3.3 栈
- 8086CPU的入栈和出栈操作都是以字为单位进行的
箭头指针指向的位置是当前的栈顶
-
段寄存器SS
:存放栈顶的段地址 -
寄存器SP
:存放栈顶的偏移地址 -
任意时刻,
SS:SP
指向栈顶元素
PUSH指令的执行过程
当执行一次push指令时,SP = SP - 2,栈顶指针向上移动两个,然后低地址存放低字节数据,高地址存放高字节数据
- 如果一个堆栈段是空的,那SP指向栈空间最高地址单元的下一个单元
假设10000H~1000FH当作堆栈段,SS=1000H,栈空间大小为16字节,栈最底部的字单元地址为?
1000:000E
3.4 栈顶越界的问题
栈空间之外的空间可能存放了具有其他用途的数据、代码等,因此要避免栈顶越界的问题
3.5 PUSH,POP指令
-
操作数可以为
段寄存器
-
PUSH DS POP ES
-
-
操作数也可以为
内存地址
-
PUSH [0] ;将0地址单元处的字入栈 POP [2] ;用2内存字单元接收出栈的数据
-
将10000H~1000FH这段空间当作栈,初始状态是空的,将AX,BX,DS中的数据入栈
MOV AX,1000
MOV SS,AX
MOV SP,0010H ;栈为空,指向最高地址单元的下一个单元
PUSH AX
PUSH BX
PUSH DS
-
PUSH指令是先使SP-2,再放入值
-
POP指令是先把值复制出来,再使SP+2
3.6 栈段
如果将10000H~1FFFFH这段空间当作堆栈段,初始状态为空,此时,SS=1000H,SP=?
SP指向最高地址单元的下一个地址,栈中只有一个元素时,SP=FFFEH,栈中没有元素时相当于栈中的唯一一个元素出栈,SP=SP+2,所以SP=0
所以栈顶的变化范围是0~FFFFH,从栈空时候的SP=0,一直压栈,直到栈满时SP=0;如果再次压栈,栈顶将环绕,覆盖了原来栈中的内容
4 第一个程序
4.1 段结束、程序结束、程序返回
段结束
段名 ends (多的s可以理解为segment)
程序结束
end
程序返回
;在程序末尾添加返回的程序段
MOV AX,4C00H
INT 21H
4.2 关于编译和链接
masm 1.asm ;编译,生成.obj目标文件
link 1.obj ;链接,生成.exe可执行文件
1.exe ;运行
4.3 EXE文件中的程序加载过程
- 程序加载后,DS中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为 DS:0
- 这个内存区的前256个字节中存放的是PSP,DOS用来和程序通信
- 从256字节处向后的空间存放的是程序 DS+10H:0
5 [BX]和LOOP指令
5.1 MOV AX,[]的说明
在debug中可以直接用 MOV AX,[0]
但是在编译器中的话编译器会看成 MOV AX,0
因此要用通用寄存器间接寻址
MOV BX,0
MOV AX,[BX]
5.2 描述性符号"()"
用(X)表示X中存放的内容
(AX) = 0010H
5.3 LOOP指令
LOOP 标号
1 (CX) = (CX) - 1
2 判断CX中的值,不为零则转到标号处执行程序,为零则向下执行
;计算2^12
ASSUME CS:CODE
CODE SEGMENT
START:MOV AX,DATA
MOV DS,AX
MOV AX,2
MOV CX,11
ADD1: ADD AX,AX
LOOP ADD1
MOV AX,4C00H
INT 21H
CODE ENDS
END
ASSUME DS:CODE
CODE SEGMENT
START:
MOV AX,0FFFFH ;在汇编源程序中,数据不能以字母开头,加0
MOV DS,AX
MOV BX,6
MOV AH,0
MOV AL,[BX] ;用BX间接作为偏移地址或者用DS:[6]
MOV DX,0
MOV CX,3
ADD1:
ADD DX,AX
LOOP ADD1
MOV AX,4C00H
INT 21H
CODE ENDS
END
ASSUME DS:CODE
CODE SEGMENT
START:
MOV AX,OFFFH
MOV DS,AX
MOV BX,0
MOV DX,0
MOV CX,12
ADD1:
MOV AL,[BX]
MOV AH,0
ADD DX,AX
INC BX
LOOP ADD1
MOV AX,4C00H
INT 21H
CODE ENDS
END
MOV BX,0
MOV CX,12
S:
MOV AX,0FFFH
MOV DS,AX
MOV DL,[BX]
MOV AX,0020H
MOV DS,AX
MOV [BX],DL
INC BX
LOOP S
MOV AX,4C00H
INT 21H
;********************************
;为了提高效率,可以用ES,将目标段赋给ES,就不用改变DS
6 包含多个段的程序
6.1 在代码段中使用数据
ASSUME CS:CODE
CODE SEGMENT
DW 0001H,0FF1H,3204H
·
·
·
CODE ENDS
END
因为DW定义的数据处于代码段最开始,段地址肯定为CS,偏移地址就是从0开始。0,2,4
6.2 在代码段中使用栈
;***********逆序****************
ASSUME CS:CODE
CODE SEGMENT
DW 1234H,0456H,0789H,0ABCH,0DEFH
DW 0,0,0,0,0 ;通过定义数据在代码段中开辟一部分空间
;作为堆栈
START:MOV AX,CS
MOV SS,AX
MOV BX,0
MOV AX,0
MOV SP,20 ;设置栈顶SS:SP指向CS:20,起始是最高地址单元+1
;0-9 + 1
MOV CX,5
s: PUSH CS:[BX]
INC BX
INC BX ;注意是字型数据,要+2
LOOP S
MOV BX,0
MOV CX,5
S1: POP CS:[BX]
INC BX
INC BX
LOOP S1
MOV AX,4C00H
INT 21H
CODE ENDS
END START
6.3 将数据、代码、栈放入不同段
DATA SEGMENT
DB ……
DATA ENDS
STACK SEGMENT
DB 0,0,0,0,0
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:MOV AX,STACK
MOV SS,AX
MOV AX,DATA
MOV DS,AX
PUSH DS:[0]
PUSH DS:[2]
POP DS:[2]
POP DS:[0]
MOC AX,4C00H
INT 21H
CODE ENDS
END START
- 如果段中的数据占N个字节,则程序加载后,该段实际占有的空间为 16*(N/16+1)
7 更灵活定位内存地址
7.1 ASCII码
-
ASCII码只需要一个字节就能存储
-
0→30H,A→41H,a→61H
-
DB 'unIX'
相当于DB 75H,6EH,49H,58H
-
大写和小写字母的ASCII码
- 二进制表示对应位只有 D 5 D_5 D5位不同,大写为0,小写为1
- 十六进制对应位相差20H
7.2 [BX+IDATA]
MOV AX,[BX+200]
还可以写成一下三种形式
MOV AX,[200+BX]
MOV AX,200[BX]
MOV AX,[BX].200
7.3 SI和DI
-
SI和DI是跟BX功能相近的寄存器,但不能分成两个8位寄存器使用
-
也可以与BX组合→
[BX+SI]
or[BX][SI]
7.4 汇编语言实现循环嵌套
;将DATA段中的每个单词改为大写字母
DATA SEGMENT
DB 'ibm '
DB 'dec '
DB 'dos '
DB 'vax '
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX,DATA
MOV DS,AX
MOV BX,0
MOV CX,4
LOOP2:
MOV DX,CX ;用DX暂存CX的当前值
MOV SI,0
MOV CX,3
LOOP1:
MOV AL,[BX+SI]
AND AL,0DFH
MOV [BX+SI],AL
INC SI
LOOP LOOP1
ADD BX,16
MOV CX,DX
LOOP LOOP2 ;先执行CX = CX - 1 再判断是否为零
MOV AX,4C00H
INT 21H
CODE ENDS
END START
如果寄存器在程序中都被使用,无法用来暂存数据怎么办?
1开辟一段内存空间,用这段空间来暂存
DATA SEGMENT
DW 0 ;用来存放CX的值
2利用堆栈
7.5 小结
1在8086CPU中,只有BX,BP,SI,DI可以在**[]**中来进行内存单元的寻址
2在**[]**中,这4个寄存器可以单个出现,或只能以四种组合出现:
BX和SI、BX和DI、BP和SI、BP和DI
3只要在**[]中使用寄存器BP**,指令中没有明显给出段地址,段地址默认在SS中
8 数据处理
8.1 指令处理数据的长度
- 在没有寄存器名存在的情况下,用操作符 X PTR 指明内存单元的长度,X可以是WORD或BYTE
8.2 寻址方式的综合应用
-
一般,可以用 [BX+i+SI] 的方式来访问结构体中的元素
用bx定位整个结构体,用i定位结构体中某一个数组项,用SI定位数组项中的每一个元素
汇编提供了更为贴切的书写方式,如
[BX].i[SI]
8.3 DIV指令
指令格式
DIV reg
DIV 内存单元
8.4 伪指令DD
DD表示定义双字变量
DATA SEGMENT
DD 1
DATA ENDS
定义数据为00000001H,在DATA:0处占两个字
8.3 DUP
使用格式
DB 重复的次数 DUP(重复的字节型数据)
8.4 实验
9 转移指令
9.1 OFFSET操作符
- 取得标号的偏移地址
MOV AX,OFFSET START
9.2 JMP指令补充
- 无条件转移指令
JMP SHORT 标号
;SHORT指明此处为8位位移
实现段内短转移,对IP修改范围为-128~127,即向前转移最多越过128个字节,向后转移最多越过127个字节
JMP NEAR PTR 标号
实现段内近转移,(IP)=(IP)+16位位移,范围为—32769~32767
JMO FAT PTR 标号
实现段间转移,又称为远转移,修改CS和IP
转移地址在内存中的使用格式
(1)JMP WORD PTR 内存单元地址
(段内转移)
功能:内存单元地址处存放的一个字是转移的目的偏移地址
(2)JMP DWORD PTR
(段间转移)
9.7 JCXZ指令
- 当CX为0时跳转
if((CX)==0) JMP SHORT 标号
9.8 显存(拓展)
10 CALL和RET
10.1 RET和RETF
RET
- RET指令用栈中的数据修改IP的内容,实现段内转移,执行时
- (IP)=((SS)*16+(SP))
- (SP)=SP+2
相当于
POP IP
例
RETF
- 用栈中的数据修改CS和IP的内容,实现远转移,顺序执行以下操作
- (IP)=((SS)*16+SP)
- (SP)=(SP)+2
- (CS)=((SS)*16+(SP))
- (SP)=(SP)+2
相当于
POP IP
POP CS
例
10.2 CALL指令
- 将当前的IP压栈后,转到标号处执行指令
- (SP)=(SP)-2
- ((SS)*16+(SP))=(IP)
- (IP)=(IP)+16位位移
CALL将下一条指令的地址入栈,执行完函数后RET从栈中取指令地址跳回去
CALL FAR PTR 标号
相当于
PUSH CS
PUSH IP
JMP FAR PTR 标号
10.3 具体应用
10.4 MUL指令
相乘的两个数要么都是8位,要么都是16位
10.5 参数和结果传递
;************************
;子程序
;说明:计算N的3次方
;参数:(BX)=N
;结果:(DX:AX)=N^3
;*************************
cube:
MOV AX,BX
MUL BX
MUL BX
RET
- 在子程序中使用寄存器之前最好将它们先入栈,然后寄存器就可以充当子程序中的局部变量
11 标志寄存器
11.1 ZF标志
零标志位
- 指令执行后结果为0,则ZF=1,反之,ZF=0
- ADD,CMP等指令都会影响,只看结果是否为0
11.2 PF标志
奇偶校验位
- 指令执行后,结果所有二进制位中1的个数
- 为偶数,PF=1
- 为奇数,PF=0
11.3 SF标志
符号标志位
-
指令执行后
- 结果为负,SF=1
- 结果为正,SF=0
-
将数据当作无符号数来运算则其值没有意义
11.4 CF标志位
进位(借位)标志位
- 指令执行无符号数发生进位或借位后,置1,其他情况置0
11.5 标志位在DEBUG中的表示
11.6 OF标志
溢出标志位
- 进行有符号数运算时,结果超出机器所能表示的范围称为溢出
- 符号位改变了
11.7 ADC指令
带进位的加法指令
11.8 SBB指令
带进位的减法指令
11.9 CMP指令
比较指令
相当于减法指令,但不保存结果只影响标志寄存器
- 无符号数→观察ZF,CF
- 有符号数
- 可以通过两数相减结果正负判断大小,即观察SF,但如果结果有溢出的话会干扰,所以还要看OF,如果OF变为1了,则与观察SF判断的结果相反
- 注意大于等于的情况
11.10 条件转移指令小结
无符号数
指令 | 含义 | 检测的相关标志位 |
---|---|---|
JE | 等于则转移 | ZF=1 |
JNE | 不等于则转移 | ZF=0 |
JB | 低于则转移 | CF=1 |
JNB | 不低于则转移 | CF=0 |
JA | 高于则转移 | CF=0,ZF=0 |
JNA | 不高于则转移 | CF=0或ZF=1 |
- E:表示Equal
- NE:表示Not Equal
- B:表示Below
- NB:表示Not Below
- A:表示Above
- NA:表示Not Above
11.11 DF标志和MOVSB
DF方向标志位
CDL指令和STD指令
- 在串处理指令中,控制每次操作后SI、DI的增减
- DF=0,每次操作后SI、DI递增
- DF=1,……递减
- CLD指令:将DF置0 clear DF
- STD指令:将DF置1 set DF
串传送指令
- MOVSB
- MOVSW
- 传送一个字,SI和DI递增2或递减2
;用串传送指令,将DATA段中的第一个字符串复制到它后面的空间中
DATA SEGMENT
DB 'Welcome to masm!'
DB 16 DUP(0)
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV SI,0
MOV DI,16
MOV CX,16
CLD
REP MOVSB
MOV AX,4C00H
INT 21H
CODE ENDS
END START
11.12 PUSHF和POPF
PUSHF
将所有标志寄存器的值入栈
POPF
将栈中数据弹出,送入所有标志寄存器
12 内部中断
12.1 中断向量表
-
CPU用8位的终端类型码通过中断向量表找到相应的中断处理程序的入口地址
- 有8位,即256个中断源
-
内存0000:0000到0000:03FF的1024个单元中存放着中断向量表
- 一个中断源要4个字节,2个存段地址,2个存偏移地址
中断过程
12.2 中断处理程序
12.2.1 编写步骤
12.2.2 除法错误中断处理
执行DIV指令时,如果发生除法溢出错误(结果无法存放),将产生0号中断
修改0号中断执行程序
assume cs:code
code segment
start:
mov ax,cs
mov ds,ax
mov si,offset do0 ;设置ds:si指向源地址
mov ax,0
mov es,ax
mov di,200h ;设置es:di指向目的地址
mov cx,offset do0end-offset do0 ;设置cx为传输长度
cld ;设置传输方向为正
rep movsb
;设置中断向量表
mov ax,4c00h
int 21h
do0:
;显示字符串“Welcome to Fishc.com!”
mov ax,4c00h
int 21h
do0end:
nop
code ends
end start
通过编译器"-"计算do0函数的代码长度
12.3 单步中断
CPU执行完一条指令后,检测标志寄存器TF位为1则产生单步中断,中断类型码为1
13 INT指令
CPU执行INT N
指令,引发N号中断
13.1 对INT、IRET和栈的深入理解
用INT触发中断实现LOOP的功能
13.2 INT 21H
程序返回功能
- 21号中断中还有很多子程序,其中的4CH号程序功能就是程序返回功能,如下
MOV AH,4CH ;程序返回
MOV AL,0 ;返回值
INT 21H
光标位置显示字符串功能
;DS:DX指向字符串,要显示的字符串需用“$”作为结束符
MOV AH,9 ;功能号9,表示光标位置显示字符串
INT 21H
14 端口
14.1 端口的读写
IN和OUT指令
IN AL,60H
从端口60H读数据放到AL中OUT 21H,AL
将AL中的数据放入端口21H中
14.2 CMOS RAM芯片
14.3 SHL和SHR指令
逻辑左移指令SHL
(1) 将一个寄存器或内存单元中的数据向左移位
(2) 将最后移出的一位写入CF中
- 移动的位数大于1时,必须将移动位数放在CL中
- 左移一位相当于×2
逻辑右移指令SHR
同理
14.4 BCD码相关
15 外部中断
15.1 可屏蔽中断和不可屏蔽中断
可屏蔽中断
- 指令
STI
,将IF置1 - 指令
CLI
,将IF置0
不可屏蔽中断
15.2 DELAY函数
DELAY:
PUSH AX
PUSH DX
MOV DX,1000H ;循环10000000H次
MOV AX,0
S1:
SUB AX,1
SBB DX,0
CMP AX,0
JNE S1
CMP DX,0
JNE S1
POP DX
POP AX
RET
16 直接定址表
16.1 数据标号
- 能够存储数据单元的地址和长度
MOV AL,B
会报错,因为B代表的内存单元是字单元,但AL是8位的
具体应用
例如,如果要将输入的字符显示在屏幕上,根据数值转化成ASCII码要经过两个区间段的映射,如16.2,因此可以直接用数据标号建立一个表存放0-F的字符,然后直接通过标号索引调用这张表
SHOWBYTE:
JMP SHORT SHOW
TABLE DB '0123456ABCDEF'
SHOW:
PUSH BX
PUSH ES
MOV AH,AL ;START:MOV AL,0EH
SHR AH,1
SHR AH,1
SHR AH,1
SHR AH,1 ;右移4位,AH中得到高位的值
AND AL,00001111B ;AL中为低四位的值
MOV AL,AH
MOV BH,0
MOV AH,TABLE[BX] ;用高四位作为相对于TABLE的偏移,取得对应字符
·
·
·
16.2 数值对应ASCII码
0-9的ASCII
+30H
10-15的ASCII
+37H
17 使用BIOS进行键盘输入
17.1 键盘输入
INT 16H
中断例程有一个功能就是从键盘缓冲区中读取键盘输入,该功能编号为0
MOV AH,0
INT 16H ;读取键盘输入并将其从缓冲区中删除
;结果:(AH)=扫描码,(AL)=ASCII码
17.2 字符串的输入
-
(1) 在输入的同时需要显示这个字符串
-
(2) 一般在输入回车后,字符串输入结束
- 可以在字符串中加入0,表示字符串结束
-
(3) 能够删除已经输入的字符串
程序处理过程
1、调用INT 16H读取键盘输入
2、如果是字符,进入字符栈,显示字符栈中的所有字符,继续执行1
3、如果是退格,从字符栈中弹出一个字符,显示字符栈中所有字符,继续执行1
4、如果是Enter键,向字符栈中压入0,返回
子程序:字符栈的入栈、出栈和显示
- 参数说明
- (AH)=功能号,0表示入栈,1表示出栈,2表示显示
- DS:SI指向字符栈空间
- 0号功能:(AL)=入栈字符
- 1号功能:(AL)=返回的字符
- 2号功能:(DH)、(DL)=字符串在屏幕上显示的行、列位置
;最基本的字符串输入程序,需要具备下面的功能:
;(1) 在输入的同时需要显示这个字符串;
;(2)一般在输入回车符后,字符串输入结束;
;(3)能够删除已经输入的字符。
;编写一个接收字符串的输入子程序,实现上面三个基本功能。
;因为在输入的过程中需要显示,子程序的参数如下:
; (dh)、(dl)=字符串在屏幕上显示的行、列位置;
; ds:si 指向字符串的存储空间,字符串以O 为结尾符。
assume cs:code
code segment
start:
call getstr
return:
mov ax,4c00h
int 21h
;完整的接收字符串输入的子程序
getstr:
push ax
getstrs:
mov ah,0
int 16h
cmp al,20h
jb nochar ;判断的是ASCII码小于0,说明不是字符
mov ah,0;
call charstack ;字符入栈
mov ah,2
call charstack ;显示栈中的字符
jmp getstrs
nochar:
cmp ah,0eh ;退格键的扫描码
je backspace
cmp ah,1ch ;回车键的扫描码
je enter
jmp getstrs
backspace: ;退格
mov ah,1
call charstack ;字符出栈
mov ah,2
call charstack ;显示栈中的字符
jmp getstrs
enter: ;回车
mov al,0
mov ah,0
call charstack ;0入栈
mov ah,2
call charstack ;显示栈中的字符
pop ax
ret ;getstr ends
;功能子程序实现
charstack:
jmp short charstart
table dw charpush,charpop,charshow
top dw 0 ;栈顶
charstart:
push bx
push dx
push di
push es
cmp ah,2
ja sret
mov bl,ah
mov bh,0
add bx,bx
jmp word ptr table[bx]
charpush:
mov bx,top
mov [si][bx],al
inc top
jmp sret
charpop:
cmp top,0
je sret
dec top
mov bx,top
mov al,[si][bx]
jmp sret
charshow:
mov bx,0b800h
mov es,bx
mov al,160
mov ah,0
mul dh
mov di,ax
add dl,dl
mov dh,0
add di,dx
mov bx,0
charshows:
cmp bx,top
jne noempty
mov byte ptr es:[di],' '
jmp sret
noempty:
mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+2],' '
inc bx
add di,2
jmp charshows
sret:
pop es
pop di
pop dx
pop bx
ret
code ends
end start
可编程接口芯片
7.1 8255
7.2 8253
中断
I/O
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)