汇编和接口

寄存器:

以8086为例:

寄存器都是16位

通用寄存器 AX, BX, CX, DX
每个寄存器又可以分为高8位和低8位:如AX -> AH, AL 每个寄存器都可以独立使用

处理数据的尺寸
字节 byte 8位 字 word 16位

简单的汇编指令
mov ax bx ax <- bx
add ax bx ax <- ax + bx
jmp 3:0b16 CS <- 3h IP <- 0b16h
jmp ax ip <- ax

16位cpu:
1.运算器一次最多处理16位数据
2.寄存器最大宽度位16
3.reg和alu之间的数据通路为16位

地址:(内存中没有段的划分)
20位物理地址 16位段地址 16位偏移地址 -> 一个段的最大长度为 2 << 16 = 64kb
20位地址只在内存中使用
物理地址 = 段地址 << 4 + 偏移地址

段寄存器
CS DS SS ES (段:segment)

CS和IP:
CS 代码段寄存器 IP 指令指针寄存器
读取一个指令后IP自动增加一个指令的长度
启动,复位后 CS = FFFFH IP= 0000H

代码段
最大64kb, 起始地址16倍数

模块化程序设计的相关问题

例子:简单的模块实例,设计一个子程序,根据提供的N,计算N的三次方

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
assmume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends

code segment
start:
mov ax, data
mov ds, ax
mov si, 0
mov di, 16

mov cx, 8
s:
mov bx, [si]
call cube
mov [di], ax
mov [di].2, ax
add si, 2
add di, 4
loop s

mov ax, 4c00h
int 21h

cube:
mov ax, bx
mul bx
mul bx
ret

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
assume cs:code

data segment
db 'word', 0
db 'unix', 0
db 'wind', 0
db 'good', 0
data ends

code segment

start:
mov ax, data
mov ds, ax
mov bx, 0

mov cx, 4
s:
mov si, bx
call capital
add bx, 5
loop s

mov ax, 4c00h
int 21h

capital
push 1

汇编代码の使用教程

retretf

ret 相当于,
pop ip
适用于近迁移

retf 相当于
pop ip
pop cs
适用于远迁移

call

CPU执行call操作的时候进行两步操作:
1.将当前的CS和IP压栈,或者只压栈当前的IP
2.转移

  • 第一种情况:call标号
    相当于:
    push IP
    jmp near ptr 标号

  • 第二种情况:call far ptr 标号
    相当于:
    push CS push IP jmp far ptr 标号`

  • 第三种情况:call 16位reg
    相当于:
    push IP
    jmp 16位reg

  • 第四种情况:call word ptr 内存单元地址

    相当于:

    push IP
    jmp word ptr 内存单元地址
    如果是dword的话
    call dword ptr 内存单元地址
    相当于:
    push CS
    push IP
    jmp dword ptr 内存单元地址

mul

格式如下:
mul reg
mul 内存单元
分两种情况:

如果是两个8位相乘,那么结果全部放在AX中。
如果是两个16位相乘,那么结果的高16位放在DX中,低16位放在AX中。

adc

adc 带进位加法指令
用法:adc ax, bx
相当于计算:
(ax) = (ax) + (bx) + CF

sbb

sbb 带借位的减法
用法:sbb ax, bx
相当于计算:
(ax) = (ax) - (bx) - CF

cmp

cmp 是比较指令, 本质上是不保存结果的减法指令
用法:cmp ax, bx
先进行计算ax - bx,计算后
![[Pasted image 20260426232144.png]]
对应的跳转指令如下:
![[Pasted image 20260426232305.png]]

pushf和popf

pushf:标志寄存器入栈
popf:标志寄存器出栈

标志寄存器Related

  1. ZF寄存器:判断相关指令结果是否为0,是0则ZF = 1

  2. PF寄存器:奇偶标准位,记录相关指令执行后结果的所以bit中1的个数是否为偶数,如果是偶数则PF = 1,16位寄存器进行操作时,只看低8位。

  3. SF寄存器,记录相关指令执行后结果是否位负,如果为负则SF = 1

  4. CF寄存器,在进行无符号数运算的时候,记录运算结果最高有效位向更高位的进位值

  5. OF寄存器,在进行有符号数的运算的时候,如果结果超过机器表示范围,称为溢出

  6. DF寄存器和串传送指令:
    df是方向标志位,在传处理指令中控制si, di的增减

    1. movsb

      相当于执行下面几步操作
      es:[di] = ds:[si]
      if df = 0:

      `(si) = (si) + 1`
      `(di) = (di) + 1`
      

      if df = 1:

      `(si) = (si) - 1`
      `(di) = (di) - 1`
      

      使用格式为:rep movsb
      相当于:
      s:movsb
      loop s
      所以使用这个指令的时候要设置cx, si, di, es, ds

      1. movsw

        具体操作与movsb相同,si, di的步长改为2

      2. df的设置指令:

        cld: df <- 0
        sld: df <- 1

内中断

cpu发生以下情况时,将产生中断信息

  1. 除法错误,如div的指令溢出 –中断码0
  2. 单步执行 –中断码1
  3. 执行into 指令 –中断码4
  4. 执行int指令 –中断码为指令参数

中断向量表:

储存在内存的0000:00000000:03FF 这1024个单元中

中断过程(8086CPU):

  • 从中断信息中获取中断类型码

  • 标志寄存器入栈(中断过程要改变标志寄存器的值)

  • 设置标志寄存器第八位的TF和第九位IF的值为0

  • CS入栈

  • IP入栈

  • 从内存地址为中断类型码 * 4 和中断类型码* 4 + 2 两个字单元中读取中断处理程序的入口地址,设置CSIP
    相当于:

    1. 获取终端类型码N
    2. pushf
    3. TF = 0, IF = 0
    4. push CS
    5. push IP
    6 (IP) = (N * 4), (CS) = (N * 4 + 2)

  • 中端处理程序和iret 指令

    iret 指令相当于:

      `pop IP`
      `pop CS`
      `popf`
    

编程处理0号中断

现在我们考虑,重新写一个0号中断处理程序,功能是在拼命中间显示”overflow!”,然后返回系统。

为了实现这个功能我们来进行分析:

  1. 当除法发生溢出的时候,产生0号中断信息,从而引发中断过程。

    • 此时CPU进行以下工作:
      (1). 获取中断类型码0;
      (2). 标志寄存器入栈,TF和IF设置为0
      (3). CS, IP入栈
      (4). (IP) = (0 * 4), (CS) = (0 * 4 + 2)

    • 可见中断0发生的时候,CPU将转去执行中断处理程序,只要按照以下步骤编写中断处理程序,当中断0发生的时候,即可显示“overflow!”

      1. 相关处理;
      2. 向显示缓冲区宋字符串”overflow!”;
      3. 返回DOS
        我们将这段程序成为do0
    • 现在问题是:do0应该放在内存中。因为除法溢出随时有可能发生,CPU随时可能将CS:IP指向do0的入口,执行程序。

    • 内存0000:0000-0000:03ff,大小为1kb的空间是存放中断程序入口地址的中断向量表。8086支持256个中断,但是实际上系统要处理的中断远没有256个。一般情况下,从0000:0200-0000:02ff的256个字节对于空间的中断向量表是空的,我们可以把do0放到0000:0200后
      程序的整体框架如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      assume cs:code

      code segment

      start:
      do0安装程序
      设置中断向量表
      mov ax, 4c00h
      int 21h

      do0:
      显示字符串"overflow!"
      mov ax, 4c00h
      int 21h

      code ends

      end start

do0的安装

可以用movsb指令,将do0的代码送入0:200处。

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
assume cs:code
code segment

start:
mov ax, cs
mov ds, ax
mov si, offset do0

mov ax, 0
mov es, ax
mov di, 200h

mov cx, offset do0end-offset do0

cld
rep movsb

设置中断向量表

mov ax, 4c00h
int 21h

do0:
显示字符串"overflow!"
mov ax, 4c00h
int 21h
do0end:nop

code ends
end start

do0

按照我们一贯的思路:

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:code

data segment
db "overflow!"
data ends

code segment

start:
mov ax, cs
mov ds, ax
mov si, ....

看似很合理,实际上大错特错。”overflow!”在程序的data段中,当程序执行完后返回,它所占用的空间会被系统是否,其中存放的字符串很肯可能被其他的信息覆盖。
正确的程序如下:

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
asuume cs:code

code segment
start:
mov ax, cs
mov ds, ax
mov si, offset do0
mov ax, 0
mov es, cx
mov di, 200h
mov cx, offset do0end - offset do0

cld
rep movsb

mov ax, 0
mov es, ax
mov word ptr es:[0 * 4], 200h
mov word ptr es:[0 * 4 + 2], 0

mov ax, 4c00h
int 21h

do0:
jmp short do0start
db "overflow!"

do0start:
mov ax, cs
mov ds, ax
mov si, 202h

mov ax, 0b800h
mov es, ax
mov di, 12*160+36*2

mov cx, 9
s:
mov al, [si]
mov es:[di], al
int si
add di, 2
loop s

mov ax, 4c00h
int 21h
do0end:nop
code ends
end start

int指令

int指令

int指令的格式为:int n,n为中断类型码,他的功能是引发中断过程。
CPU执行int n指令,相当于引发一个n号中断的过程,执行过程如下:

  • 取中断类型码n;
  • 标志寄存器入栈,IF = 0,TF = 0;
  • CS,IP入栈
  • (IP) = (n * 4), (CS) = (n * 4 + 2)

编写供应用程序调用的中断实例

问题1:编写、安装中断7ch的中断例程

功能:求一word数据的平方
参数:(ax) = 要计算的数据
返回值:dx,ax中存放高16位和低16位
应用实例:求2 * 3456 ^ 2

1
2
3
4
5
6
7
8
9
10
11
12
13
assume cs:code

code segment
start:
mov ax, 3456
int 7ch
add ax, ax
adc dx, dx
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
assume cs:code

code segment
start:
mov ax, cs
mov ds, ax
mov si, offset sqr
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset sqrend - offset sqr
cld
rep movsb
;把ds:[si] -> es:[di] 长度为cs

mov ax, 0
mov es, ax
mov word ptr es:[7ch * 4], 200h
mov word ptr es:[7ch * 4 + 2], 0
;设置中断向量表

mov ax, 4c00h
int 21h

sql:
mul ax
iret
sqrend:
nop

code ends
end start

注意:在中断例程sqr的最后,要实验iret指令。用汇编语言描述,iret的指令功能如下:
pop IP
pop CS
popf

问题2:

将一个和全是字母,以0结尾的字符串,转化为大写

参数:ds:si 指向字符串的首地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code

data segment
db 'coversation', 0
data ends

code segment
start:
mov ax, data
mov ds, ax
mov si, 0
int 7ch

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
assume cs:code
code segment
start:
mov ax, cs
mov ds, ax
mov si, 0
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset capital - offset capital_end
cld
rep movsb

mov ax, 0
mov es, ax
mov word ptr es:[7ch * 4], 0
mov word ptr es:[7ch * 4 + 2], 200h

mov ax, 4c00h
int 21h

capital:
push ds
push si
capital_s0:
mov cl, ds:[si]
mov ch, 0
cmp cx, 0
je ok
and byte ptr [si], 11011111b
inc si
jmp short capital_s0
ok:
push si
push ds
iret
capital_end:
nop

code ends
end start

int、iret和栈的深入理解

问题:用7ch中断实例完成loop指令的功能

loop s的执行需要两个信息,循环次数和到s的位移,所以,7ch中断实例要求完成loop的功能,也需要这两个信息作为参数。我们用cx存放参数,用bx存放位移。

应用举例,在屏幕中间显示80个” ! “

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code
start:
mov ax, 0b800h
mov es, ax
mov di, 160 * 12

mov bx, offset s - offset se
mov cx, 80
s:
mov byte ptr es:[si], '!'
add si, 2
int 7ch
se:nop

mov ax, 4c00h
int 21h


code ends
end start

中断实例如下:

1
2
3
4
5
6
7
8
9
lp:
push bp
mov bp, sp ;取当前的栈地址
dec cx
jcxz lpret
add [bp + 2], bx
lpret:
pop bp
iret

DOS中断例程应用

 int 21h中断例程是DOS提供的中断例程,其中包包含了DOS提供过程序员在编程时调用的子程序。

我们前面使用的int 21h是中断例程的4ch号功能,即程序返回功能

1
2
3
mov ah, 4ch ;return
mov al, 0 ;0
int 21h

(ah) = 4ch 表示调用中断例程的4ch号子程序,功能为程序返回,可以提供返回值作为参数

接下来看一看int 21h中断例程在光标位置显示字符串的功能:

1
2
3
ds:dx 指向字符串   ;要显示的字符串需要用$作为结束符
mov ah, 9
int 21h

编程,在5行12列显示字符串”Welcome to masm”

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
assume cs:code

data segment
db 'Welcome to masm', '$'
data ends

code segment
start:
mov ah, 2 ;置光标
mov bh, 0 ;第0页
mov dh, 5 ;dh中放行号
mov dl, 12;dl中放列号
int 10h

mov ax, data
mov ds, ax
mov dx, 0
mov ah, 9
int 21h

mov ax, 4c00h
int 21h

code ends
end start

端口

CPU可以直接读三个地方的数据:

  1. CPU内部的寄存器

  2. 内存单元

  3. 端口

端口的读写

CPU通过端口地址来定位端口。端口所在的芯片与CPU通过总线相连,端口地址和内存地址一样,通过地址总线来传送。PC系统中,CPU最多可以定位64KB个不同的端口,则端口的地址范围为0-65535

端口的读写指令只有两条

in 和 out

访问端口:

in al, 60h 从60h端口读入一个字节

  • CPU通过地址线将地址信息60h发出;

  • CPU通过控制线发出端口读命令,选中端口所在芯片并且告诉他要读

  • 端口所在的芯片将60h端口的数据通过数据线送入CPU

in 和 out 指令只能al 或者 ax寄存器存放从端口得到的数据或者要发给端口的数据

对0-255以内的端口进行读写的时候:

1
2
in al, 20h
out 20h, al

对256-65535的端口进行读写时,端口号放在dx中

1
2
3
mov dx, 3f8h
in al, dx
out dx, al

CMOS RAM芯片

PC机中,有一个CMOS RAM芯片,特征如下:

  • 包含一个实时钟和一个有128个储存单元的RAM存储器

  • 芯片靠电池供电,关机信息不丢失

  • 128个字节中,实时种占用0-0dh单元保存时间信息,其余大部分单元用于保存系统配置信息,供系统启动时BIOS程序读取

  • 芯片内部有两个端口,70h和71h来读写CMOS RAM

  • 70h为地址端口,存放要访问CMOS RAM单元的地址数据;71h为数据端口,存放从CMOS RAM中要读或者要写的数据。

可见,CPU对CMOS读写分两步,比如读取CMOS RAM的2号单元:

(1) 将2送入端口70h

(2)从端口71h读出2号单元的数据

shl 和 shr 指令

shl 和 shr 是逻辑移位

shl是逻辑左移,功能为:

  1. 将一个寄存器或者内存单元的数据向左移位

  2. 将最后移出的一位写入CF

  3. 最低位用0补充

  4. 如果移动位数大于1,必须把移动位数放在cl里

shr是逻辑右移,功能为

  1. 将一个寄存器或者内存单元的数据向右移位

  2. 将最后移出的一位写入CF

  3. 最高位用0补充

  4. 如果移动位数大于1,必须把移动位数放在cl里

CMOS RAM中存储时间的信息

CMOS RAM中存放着年,月,日,时,分,秒。这六个信息的长度都是一个字节,存放单元为:

秒:0    分:2    时:4    日:7    月:8    年:9

这些数据以bcd码的方式存放,一个字节可以表示两个BCD码,则CMOS RAM存储时间信息的单元里,存储了用两个BCD码表示的两位十进制数,高4位BCD码表示十位,低4位BCD码表示各位。例如:0001 0100b表示 14

编程:在屏幕中间显示当前月份的bcd码

  • 从CMOS RAM的8号单元读取当前月份的BCD码:

    1
    2
    3
    4
    mov al, 8
    out 70h, al ;将内存单元号写入70h


1

1

1

1

1

1

in al, 71h ;从71h端口读出指定单元的数据

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

- 将用BCD码表示对月份以十进制的形式显示在屏幕上

可以看到:BCD码 + 30h = 十进制数对应的ASCⅡ码

完整程序如下:

```nasm
assume cs:code
code segment
start:
mov al, 8
out 70h, al
in al, 71h

mov ah, al
mov cl, 4
shl ah, cl
and al, 00001111b

add ah, 30h
add al, 30h

mov bx, 0b800h
mov es, bx
mov byte ptr es:[160 * 12 + 40 * 2], ah
mov byte ptr es:[160 * 12 + 40 * 2 + 2], al

mov ax, 4c00h
int 21h


code ends
end start

外中断

接口芯片和端口

在pc系统的接口卡和主板上,装有各种有各种接口芯片。这些外设接口芯片内部有若干寄存器,CPU把这些寄存器当作端口来访问。

外中断信息

现在我们知道,外设的输入被存放在端口中,可是外设的输入随时有可能到达,CPU如何及时地知道,并且进行处理呢?

CPU提供中断机制来满足这些要求。与内中断不同,还有一种中断信息,来自于CPU外部,当CPU外部有需要处理的事情发生的时候,相关的芯片将向CPU发出相应的中断信息。CPU在执行了当前的指令后,可以检测到发送过来的中断信息,引发中断过程。

在CPU系统中,外中断源一共有以下两类

一. 可屏蔽中断

可屏蔽中断是CPU可以不响应的外中断。CPU是否响应,要看标志寄存器IF位的设置。如果IF = 1,CPU在执行完当前指令后会响应中断;如果IF = 0,则不响应可屏蔽中断。

回忆一下内中断的中断过程:

(1) 取中断状态码 n

(2) 标志寄存器入栈

(3) CS,IP入栈

(4) (IP) = (n * 4), (CS) = (n * 4 + 2)

可屏蔽中断引发的终端过程,除了第一步的实现有所不同外,基本上与内中断的过程相同。

当然,如果中断处理程序中需要处理可屏蔽中断时,则在执行完当前指令后,立即响应,引发中断过程,可以用指令将IF设置为1。8086CPU提供的设置IF指令如下:

sti,设置IF = 1;

cli,设置IF = 0。

二. 不可屏蔽中断

不可屏蔽中断是CPU必须响应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,离开响应,引发中断过程。

对于8086CPU,不可屏蔽中断的中断类型码固定为2,所以中断过程中,不需要取中断类型码,立即响应,引发中断过程:

(1) 标志寄存器入栈,IF = 0, TF = 0;

(2) CS, IP入栈;

(3) (IP) = (8), (CS) = (0ah)

PC机键盘的处理过程

1. 键盘输入

键盘上每个键相当于一个开关,键盘中有一个芯片对键盘上的每个键的开关状态进行扫描。

按下一个键后,开关接通,芯片产生一个扫描码,扫描码说明了按下的键在键盘上的位置,扫描码被送入60h端口。

松开按下的键后,也产生一个扫描码,说明了松开的键在键盘上的位置,松开时产生的扫描码也被送入60h端口。

一般按下一个键产生的扫描码叫做通码,松开一个键产生的扫描码成为断码。扫描码长度为一个字节,通码的第7位为0,断码的第七位为1,即:

断码 = 通码 + 80h

比如,g键的通码为22h,端码为a2h

92f5f0a6-3fb0-4e9c-89d5-02921f227c81

2. 引发9号中断

键盘输入到达60h端口时,相关的芯片就会向CPU发出中断类型码为9的可屏蔽中断信息。CPU检测到该中断信息后,如果IF = 1,则响应中断,引发中断过程,转去执行int 9中断例程。

3. 执行 int 9中断例程

BIOS提供了int 9中断例程,来进行基本的键盘输入处理,主要工作如下:

  1. 读出60h端口中的扫描码;

  2. 如果是字符键的扫描码,就将该扫描码对应的字符码送入BIOS键盘缓冲区;如果是控制键,则转变为状态字节写入内存中存储状态字节的单元。

  3. 对键盘系统进行相关的控制,比如相关芯片发出应答。

  4. BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9中断例程接收的键盘输入的内存区。该内存可以存15个键盘输入,因为int 9 中断例程除了接收扫描码之外,还要产生和扫描码对应的字符码,所以在BIOS键盘缓冲区中,一个键盘输入用一个字单元存放, 高位字节存扫描码,低位字节存字符码。

e7bfd457-7345-4455-8669-2d8d34e8f49c

编写 int 9中断例程:

编程:在屏幕中间依次显示“a” ~“z”,并可以让人看清,在显示的过程中,按下esc键后,改变显示的颜色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
start:
mov ax, 0b800h
mov es, ax
mov ah, 'a'
s:
mov es:[160 * 12 + 40 * 2], ah
inc ah
cmp ah, 'z'
jna s
mov ax, 4c00h
int 21h
code ends
end start

上面的程序执行过程中,我们无法看清屏幕上的显示,因为字母之间切换的太快,无法看清。

如何在每显示一个字母后,延迟一段时间,让人看清之后,再显示下个字母呢?我们让CPU执行一段时间的空循环,因为现在CPU的速度都非常快,所以循环的次数一定要大,如下:

1
2
3
4
5
6
7
8
9
    mov dx, 10h
mov ax, 0
s:
sub ax, 1
sbb dx, 0
cmp ax, 0
jne s
cmp dx, 0
jne s

我们可以把这个程序写成一个子程序,现在我们的程序如下:

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
assume cs:code
code segment
start:
mov ax, 0b800h
mov es, ax
mov ah, 'a'
s:
mov es:[160 * 12 + 40 * 2], ah
call delay
inc ah
cmp ah, 'z'
jna s ;不大于

mov ax, 4c00h
int 21h


delay:
push ax
push dx
mov dx, 1000h
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

code ends
end start

显示”a” ~ “z”并且让人看清已经实现了,接下来怎么实现按下esc来改变颜色呢?

1.从端口60h读出键盘输入

1
in al, 60h

2.调用BIOS的int 9中断例程

这次我们不能之间使用int 9来调用,但是我们可以用别的指令来对int进行模拟

在假设要调用的中断例程入口地址在ds:0和ds:2的前提下,我们将int过程用下面几步模拟。

(1) 标志寄存器入栈;

(2) IF = 0, TF = 0

(3) CS, IP入栈

(4) (IP) = ((ds) * 16 +0), (CS) = ((ds) * 16 + 2)

对于(1)可用pushf实现

对于(2),可用以下指令实现:

1
2
3
4
5
pushf
pop ax
and ah, 11111110b
push ax
popf

则模拟int指令调用功能,调用入口地址在ds:0、ds:2中的中断例程的程序为:

1
2
3
4
5
6
7
8
pushf 

pop ax
and ah, 11111110b
push ax
pop f

call dword ptr ds:[0]

3.如果是Esc的扫描码,改变显示的颜色后返回

完整的程序如下:

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
assume cs:code 

stack segment
db 128 dup (0)
stack ends

data segment
dw 0, 0
data ends

code segment
start:
mov ax, stack
mov ss, ax
mov sp, 128

mov ax, data
mov ds, ax

mov ax, 0
mov es, ax

push es:[9 * 4]
pop ds:[0]
push es:[9 * 4 + 2]
pop ds:[2]

mov word ptr es:[9 * 4], offset int 9
mov es:[9 * 4 + 2], cs

mov ax, 0b800h
mov es, ax
mov ah, 'a'
s:
mov es:[160 * 12 + 40 * 2], ah
inc ah
cmp ah, 'z'
jna s

mov ax, 0
mov es, ax

push ds:[0]
pop es:[9 * 4]
push ds:[2]
pop es:[9 * 4 + 2]

mov ax, 4c00h
int 21h

delay:
push ax
push dx
mov dx, 1000h
mov ax, 0
s1:
sub ax
sbb dx
cmp ax, 0
jne s1
cmp dx, 0
jne s1

pop dx
pop ax
ret

;--------以下为新的int9中断例程---------
int 9:
push ax
push bx
push es

in al, 60h

pushf
pushf
pop bx
and bh, 11111110b
push bx
popf
call dword ptr ds:[0]

cmp al, 1
jne int9ret

mov ax, 0b800h
mov es, ax
inc byte ptr es:[160 * 12 + 40 * 2 + 1]

int9ret:
pop es
pop bx
pop ax
iret


code ends
end start

安装新的int9中断例程

任务:安装一个新的int9中断例程

功能:在DOS下、按F1键后改变当前屏幕显示颜色,其他的键照常处理。

(1)改变屏幕的显示颜色

改变从b8000h开始的4000个字节的所有奇地址单元的内容,当前屏幕显示的颜色就会发生改变,程序如下:

1
2
3
4
5
6
7
8
mov ax, 0b800h
mov es, ax
mov bx, 1
mov cx, 2000
s:
inc byte ptr es:[bx]
add bx, 2
loop s

(2)其他键照常处理

可调用原int 9中断例程程序,来处理其他键盘的输入。

(3)原int9中断例程入口地址的保存。

要保存原int 9中断例程的入口地址,保存在0:200单元处,防止地址丢失。

(4)新int 9中断例程的安装

我们可将新的int 9中断例程安装在0:204处

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
assume cs:code

stack segment
db 128 dup (0)
stack ends

code segment
start:
mov ax, stack
mov ss, ax
mov sp, 128

push cs
pop ds

mov ax, 0
mov es, ax

mov si, offset int9 ;设置ds:si指向源地址
mov di, 204h ;设置es:di指向目的地址
mov cx, offset int9end - offset int9 ;设置cx为传输长度
cld
rep movsb

push es:[9 * 4]
pop es:[200h]
push es:[9 * 4 + 2]
pop es:[202h] ;保存原int9中断例程的入口地址

cli
mov word ptr es:[9 * 4], 204h
mov word ptr es:[9 * 4 + 2], 0
sti

mov ax, 4c00h
int 21h

int9:
push ax
push bx
push cx
push es

in al, 60h

pushf
call dword ptr cs:[200h] ;当此中断例程执行时(CS) = 0

cmp al, 3bh ;F1的扫描码为3bh
jne int9ret

mov ax, 0b800h
mov es, ax
mov bx, 1
mov cx, 2000
s:
inc byte ptr es:[bx]
add bx, 2
loop s

int9ret:
pop es
pop cx
pop bx
pop ax
iret

int9end:
nop

code ends
end start

第16章 直接定位表

16.1 描述了单位长度的单元

前面我们,一直在代码端中使用标号来标记指令、数据、段的起始地址。比如如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:code
code segment
    a: db 1,2,3,4,5,6,7,8
    b: dw 0

start:
    mov si, offset a
    mov bx, offset b
    mov cx, 8
s:
    mov al, cs:[si]
    mov ah, 0
    add cs:[bx], ax
    inc si
    loop s

    mov ax, 4c00h
    int 21h

code ends
end start

程序中,code、a、b、start、s都是标号。这些标号仅仅表示了内存单元的地址。

但是我们还可以用一种标号,不仅表示内存单元地址,还表示内存单元的长度,即表示在此标号的单元,是一个byte,还是word,还是dw。上面的程序可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code
code segment
    a db 1,2,3,4,5,6,7,8
    b dw 0

start:
mov si, 0
mov bx, 8
mov cx, 8
s:
mov al, a[si]
mov ah, 0
add b, ax
inc si
loop s
mov ax, 4c00h
int 21h

code ends
end start

code段使用的标号a,b后面没有:,它们是同时描述内存地址和内存单元长度的标号。

标号a,表示地址code:0,且后面的单元都是字节。

标号b,表示地址code:8,且后面的单元都是字。

16.2 在其他段中使用数据标号

一般来说,我们不在代码段定义数据,而是将数据定义在其他段中。在其他段中,我们也可以用数据标号来表示存储数据单元的地址和长度。

注意,后面加有“:”的地址标号只能在代码段中使用。

下面程序将a标号的8个数据累加,结果储存在b标号处的字中。

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
assume cs:code, ds:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends

code segment
start:
mov ax, data
mov ds, ax

mov si, 0
mov cx, 8
s:
mov al, a[si]
mov ah, 0
add b, ax
inc si
loop s

mov ax, 4c00h
int 21h

code ends
end start

16.3 直接定址表

现在,我们讨论用查表的方法编写相关程序的技巧。

编写子程序,以16进制的形式在屏幕中间显示给定的字节型数据。

子程序如下:

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
showbyte:
jmp short show
table db '0123456789ABCDEF'

show:
push bx
push es

mov ah, al
shr ah, 1
shr ah, 1
shr ah, 1
shr ah, 1
and al, 00001111b

mov bl, ah
mov bh, 0
mov ah, table[bx]

mov bx, 0b800h
mov es, bx
mov es:[160*12 + 40*2], ah

mov bl, al
mov bh, 0
mov al, table[bx]

mov es:[160*12 + 40*2 + 2], al

pop es
pop bx

ret

16.4 程序入口地址的直接定址表

我们可以在直接定址表中存储子程序的地址,从而方便地实现不同的子调用。我们看以下问题:

实现一个子程序setscreem,为显示输出提供如下功能

  • 清屏;

  • 设置前景色;

  • 设置背景色;

  • 向上滚动一行。

入口参数说明如下:

  1. 用 ah 寄存器传递功能号:0表示清屏,1表示设置前景色,2表示设置背景色,3表示向上滚动一行;

  2. 对于1、2号功能,用al传递颜色值。

我们将这四个功能分别写成四个子程序。

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
sub1:
push bx
push cx
push es
mov bx, 0b800h
mov es, bx
mov bx, 0
mov cx, 2000
sub1s:
mov byte ptr es:[bx], ' '
add bx, 2
loop sub1s
pop es
pop cx
pop bx
ret
sub2:
push bx
push cx
push es

mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000
sub2s:
and byte ptr es:[bx], 11110000b
or es:[bx], al
add bx, 2
loop sub2s

pop es
pop cx
pop bx
ret
sub3:
push bx
push cx
push es
mov cl, 4
shl al, cl
mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000
sub3s:
and byte ptr es:[bx], 10001111b
or es:[bx], al
add bx, 2
loop sub3s
pop es
pop cx
pop bx
ret

sub4:
push cx
push si
push di
push es
push ds

mov si, 0b800h
mov es, si
mov ds, si
mov si, 160 ;ds:si 指向n + 1行
mov di, 0 ;es:si 指向n行
cld
mov cx, 24 ;共复制24行
sub4s:
push cx
mov cx, 160
rep movsb
pop cx
loop sub4s

mov cx, 80
mov si, 0
sub4s1:
mov byte ptr [160 * 24 + si], ' ' ;清空最后一行
add si, 2
loop sub4s1

pop ds
pop es
pop di
pop si
pop cx
ret

我们可以将这些子程序的入口地址储存在一个表中,他们在表中的位置和功能号相对应。

对应关系为:功能号 * 2 = 对应的功能子程序在地址表中的偏移。程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
setscreen:
jmp short set

table dw sub1,sub2,sub3,sub4

set:
push bx
cmp ah, 3 ;判断功能号是否大于3
ja sret
mov bl, ah
mov bh, 0
add bx, bx ;根据ah中的功能号,计算对应的子程序在table表中的偏移

call word ptr table[bx]

sret:
pop bx
ret

第17章

17.1 in9中断例程对键盘输入的处理

int9 中断例程对键盘输入的处理是:

从60h端口读出键盘输入的通码,检测状态字节是否有Shift、Ctrl等切换键按下。若没有,则将键盘输入的扫描码和通码输入缓冲区。如:键盘输入A,将A键的扫描码1eh和对应的ASCⅡ码61h写入键盘缓冲区。

1e61

17.2 使用int 16h 中断例程读取键盘缓冲区

int 16h中断例程包含一个最重要的功能就是从键盘缓冲区中读取一个键盘输入,该功能编号为0。

1
2
mov ah, 0
int 61h

结果:(ah) = 扫描码,(al) = ASCⅡ码

ah 中的内容为1Eh, al 中的内容为61h。从上面我们可以看出,int 16h 中断例程的0号功能,进行如下的工作。

(1) 检测键盘缓冲区中是否有数据;

(2) 没有则继续做第1步;

(3) 读取缓冲区第一个字单元中的键盘输入;

(4) 将读取的扫描码送入 ah,ASCII 码送入al;

(5) 将已读取的键盘输入从缓冲区中删除。

应用举例:

编程,接受用户的键盘输入,输入r,将屏幕上的字符设置成红色;输入g设置成绿色;输入b设置成蓝色。

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
assume cs:code
code segment
start:
mov ah, 0
int 16h

mov ah, 1
cmp al, 'r'
je red
cmp al, 'g'
je green
cmp al, 'b'
je blue
jmp short sret

red:
shl ah, 1
green:
shl ah, 1

blue:
mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000

code ends
end start

17.3 字符串的输入

最基本的字符串输入程序,要具备以下功能:

  • 在输入的同时要显示这个字符串;

  • 一般在输入回车符后,字符输入结束;

  • 能够删除已经输入的字符。

子程序:字符栈的入栈、出栈和显示。

参数说明: (ah)=功能号,0表示入栈,1表示出栈,2表示显示:

ds:si 指向字符栈空间;

对于0号功能: (al)=入栈字符;

对于1号功能:(al)=返回的字符;

对于2号功能: (dh)、(dl)=字符串在屏幕上显示的行、列位置。

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
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],' '

下面写出完整的接收字符串的子程序,如下:

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
getstr:
push ax
getstrs:
mov ah, 0
int 16h
cmp al, 20h
jb nochar
mov ah, 0
call charstack
jmp getstrs

nochar:
cmp ah, 0eh ;backsapce
je backspaces
cmp ah, 1ch ;enter
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
mov ah, 2
call charstack
pop ax
ret

17.4 应用int 13h中断例程对磁盘进行读写

以3.5英寸软盘为例,3.5 英寸软盘分为上下两面,每面有80个磁道,每个磁道又分为18个扇区,每个扇区的大小为512个字节。

则: 2面 * 80 磁道 * 18 扇区 * 512 字节=1440KB~1.44MB

BIOS通过了对扇区进行读写的中断例程,我们可以通过调用BIOS中断例程里访问磁盘。

BIOS提供访问磁盘的中断例程为int 13h。读取 0 面 0 道 1 扇区的内容到0:200的程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
mov ax, 0
mov es, ax
mov bx, 200h ;es:bx 指向接收从扇区读入数据的内存区

;入口参数
mov al, 1 ;读取的扇区数
mov ch, 0 ;磁道号
mov cl, 1 ;扇区号
mov dl, 0 ;驱动器号 软驱从0开始,0:软驱A,1:软驱 B;硬盘从 80h 开始,80h:硬盘 C,81h:硬盘D
mov dh, 0 ;磁头号头号(对于软盘即面号,因为一个面用一个磁头来读写)
mov ah, 2 ;2表示读扇区 3表示写扇区
int 13h

返回参数:

操作成功:(ah) = 0,(al) = 读入的扇区数

操作失败:(ah) = 出错代码


接口部分:

5.2 RAM 随机存储器

5.2.1 SRAM 静态 RAM

SRAM 的特点

Static RAM, 静态随机存储器

特点

  • 速度快、状态稳定、不需刷新、单元复杂、功耗大、价格高、常见于高速缓存Cache

SRAM 写法:

如:2114:1K x 4

表示:

  • 有1K个存储单元

  • 每个单元4bit

  • 总容量 = 1K x 4 bit = 500B 一个字节八个bit

常考:

  • 芯片的容量怎么算?

    • 单元数 x 每单元位数,再写成字节
  • 地址线根数 = log2(单元数)

  • 数据线跟数 = 每个单元的位数


5.2.2 DRAM 动态 RAM

Dynamic RAM,动态随机存储器

特点:

  • 速度比SRAM慢、用电容存、状态不稳、要定时刷新、集成度高、功耗小价格低、常见于主存

5.2.3 DRAM 为什么要刷新?

DRAM单元靠电容也没有电荷表示0/1,但问题是:电容会漏电

所以DRAM不是写进去就稳定,需要定期刷新。

刷新方式:异步刷新:

以2164A为例:

刷新间隔:2ms

2164A 共 128周期

每隔:

2ms / 128 = 15.6 us 刷新一行。


5.3 ROM

5.3.1 核心特点:

  • 只读

  • 掉电后内容不丢失

  • 存放BIOS、固定程序常量等


5.3.2 EPROM 27128

27128 = 16K x 8

引脚理解:

地址线 14根:A13 - A0 选择芯片内部的哪个单元

数据线 8根 :D7 - D0 用来输出/编程输入数据

CE:片选,决定这个芯片是否被选中

OE:输出允许,读出是数据是否出现在数据线上

PGM:编程控制

VPP:编程电压

VCC/GND:电源和地


5.3.3 EEPROM 2817A

特点:

  • 可电擦除

  • 可电写入

  • 编程前能自动擦除要写入的单元


5.4 存储器和CPU的连接

5.4.1 存储器的接口设计要考虑什么?

1
2
3
4
5
6
7
8
9
存储器和CPU连接,要考虑:

1. 地址总线怎么连
2. 数据总线怎么连
3. 控制总线怎么连
4. CPU的负载能力是否够
5. 存储器速度能否跟得上CPU时序
6. 地址空间如何分配
7. 片选信号如何产生

5.4.2 地址线:高位片选,低位片内寻址

1
2
3
4
5
低位地址线:
接到存储芯片的地址引脚,来选择芯片内部那个单元

高位地址线:
送入译码器,产生片选信号,决定选中哪一片芯片

5.4.3 片选信号CS/CE

CPU地址空间很大,但是一片芯片容量有限

所以访问某个地址的时候,系统要先判断:这个地址属于哪一片芯片?

选中芯片的信号就叫:

1
片选信号:CS 或 CE

常见的片选信号是低电平有效

1
2
CE = 0: 芯片被选中
CE = 1: 芯片不工作

5.4.4 74LS138 译码器

1
2
3
4
5
6
7
8
9
10
74LS138 = 3-8译码器

输入:
C B A 三位二进制

输出:
Y0 ~ Y7八个输出

特点:
某个时刻只有一个输出为低电平

例如输入为:1 0 0 对应十进制数为4 -> Y4 = 0,其他均为1


5.4.5 芯片扩展

1. 位扩展:

2. 字扩展

3. 字位扩展


5.4.6 三种片选方法

1. 线选法

用某几根高位地址线直接作为片选信号。

电路简单,但是很多高位地址会没有参与编码,造成地址重叠和地址空间浪费。

2. 全译码法

所以高位地址全部参与编码

不会有地址重叠,但是译码电路复杂

3. 部分译码法

只让一部分高位地址参与译码

比全译码简单,但是仍然可能产生地址重叠


6.1 I/O接口

6.1.1 为什么需要I/O接口?

CPU和存储器比较合拍,但是CPU和外设差异太大了。

1
2
3
4
5
6
7
8
9
10
11
12
CPU:
- 速度快
- 电平标准统一
- 数据格式规整
- 时序严格

外设:
- 速度慢
- 种类多
- 电平可能不同
- 数据格式可能不同
- 工作时序不统一

所以在CPU和外设之间要加:I/O接口电路。

I/O接口电路是外设和计算机之间传送信息的交接部件,每个外设都要通过接口电路才能和主机相连。


6.1.2 I/O接口的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. 数据缓冲
解决CPU快、外设慢的问题。

2. 电平转换
解决TTL、RS-232等电平不一致问题。

3. 信息格式转换
例如模拟量/数字量转换,串行/并行转换。

4. 时序控制
通过状态、应答、中断等信号协调CPU和外设。

5. 地址译码
判断CPU当前访问的是哪个I/O端口。

6. 提供读/写、中断等控制逻辑

6.1.3 I/O端口

CPU和外设交换的信息主要有三类:

1
2
3
1. 数据信息
2. 状态信息
3. 控制信息

所以端口也分为三类

  • 数据端口:存放输入/输出的数据

  • 状态端口:让CPU读取外设的状态

  • 控制/命令端口:CPU向接口发命令


6.1.4 常见的状态位

Ready:准备好。输入的时候有数据等待CPU读,输出的时候表示输出缓冲区为空,可以接受新数据。

Busy:忙。外设正在工作,暂时不能接受新数据。

Error:错误。数据传送过程中出错了。


6.1.5 I/O 端口寻址方式

1. 存储器映像寻址

1
I/O端口和内存统一编址。

优点:访存指令都能访问I/O端口缺点:占用内存地址空间

2. I/O单独编址

1
I/O端口单独拥有地址空间。CPU用专门的 IN / OUT 指令访问。

8086 系统采用 I/O 单独编址方式。


6.1.6 CPU和外设的数据传送方式

1. 程序控制方式

分两种:

  • 无条件传送:默认外设随时准备好,准备IN/OUT

  • 条件传送/查询方式:CPU先读状态口,确认Ready还是Busy,再传送数据

2. 中断方式

CPU平时做自己的事情

外设准备好之后发中断请求

CPU响应中断,执行中断服务程序完成一次数据传送

3. DMA方式

DMA = Direct Memory Access

之间从存储器中读取

特点:

  • CPU让出总线控制权

  • DMA控制器接管地址总线、数据总线、控制总线

  • 外设和内存直接批量传送数据

  • 传完后归还总线


6.2 8255A工作原理

6.2.1 什么是8255A?

8255A是可编程并行I/O接口芯片

它可以通过编程工作在三种方式:

  • 方式0:基本输入输出

  • 方式1:选通输入输出

  • 方式2:双向总线方式


6.2.2. 内部结构

8255A内部包括:

  1. 数据端口A,B,C

  2. A组和B组控制逻辑

  3. 数据总线缓冲器

  4. 读写控制逻辑

1. 三个端口:

A口:8位,PA7 ~ PA0
B口:8位,PB7 ~ PB0
C口:8位,PC7 ~ PC0

C口比较特殊,可以拆成:

  • C高:PC7 ~ PC4

  • C低:PC3 ~ PC0

2. A组和B组

A组:管理A口和C高

B组:管理B口和C低


6.2.3 端口地址选择

8255A用A1A0选择内部端口:

A0A1 选择
00 A口
01 B口
10 C口
11 控制口

若8255A的基地址为60h,则A、B、C、控制口分别为60h、61h、62h、63h。

在 8086 的 16 位系统中,如果 8255A 的 D7~D0 接低 8 位数据总线,则通常用偶地址访问。例如基地址 F0H 时,A/B/C/控制口分别是 F0H、F2H、F4H、F6H。


5.4-存储器的连接(1).pptx

Presentation

6.1-IO接口.pptx

Presentation

6.2-8255A的原理.pptx

Presentation

6.3-8255A的应用举例.pptx

Presentation

5.2+5.3-RAM&ROM存储器.pptx

Presentation

根据ppt带着我详细的学一下这几章的内容,格式用md,我方便做笔记

Thought for 43s

我先按本科《微型计算机原理与接口技术》期末复习 + 真正理解电路/编程思路来带你学。我们这批 PPT 主要是:
第5章 存储器- 5.2 RAM:SRAM、DRAM- 5.3 ROM:EPROM、EEPROM- 5.4 存储器与CPU的连接第6章 I/O接口与8255A- 6.1 I/O接口- 6.2 8255A工作原理- 6.3 8255A应用举例

下面先给你一版可直接做笔记的总框架,然后我们从 5.2 RAM 开始慢慢啃。


第5章、第6章学习总路线

一句话总览

第5章解决的问题:CPU怎样和“存储芯片”连接,并正确读写数据?第6章解决的问题:CPU怎样和“外设”连接,并通过8255A这种接口芯片输入/输出数据?

存储器连接重点是:地址线、数据线、控制线、片选信号。PPT 里强调,存储器要和 CPU 的地址、数据、控制总线正确连接,并考虑 CPU 总线负载、存储器速度、地址分配、片选和控制信号连接。

I/O 接口重点是:CPU 和外设速度、电平、格式、时序都不一样,所以中间必须有接口电路。PPT 中把 I/O 接口功能归纳为数据缓冲、电平转换、信息格式转换、时序控制、地址译码和读写/中断控制等。


5.2 RAM 随机存储器

5.2.1 SRAM 静态 RAM

1. SRAM 的特点

SRAM = Static RAM,静态随机存储器特点:- 速度快- 状态稳定- 不需要刷新- 单元电路复杂- 集成度低- 功耗较大- 价格较高- 常用于高速缓存 Cache

PPT 中明确给出 SRAM“速度快,状态稳定”,但“位单元电路复杂、集成度低、功耗较大、价格偏高”,主要用于高速缓存。

2. 常见 SRAM 芯片

2114:1K × 46116:2K × 86232:4K × 86264:8K × 862256:32K × 864C512:64K × 8

这里的写法非常重要:
2K × 8 表示:- 有 2K 个存储单元- 每个存储单元 8 位- 总容量 = 2K × 8 bit = 2KB

考试里经常考:
芯片容量 = 单元数 × 每单元位数地址线根数 = log2(单元数)数据线根数 = 每单元位数

例如:
6116 = 2K × 82K = 2048 = 2^11所以地址线需要 11 根:A10 ~ A0每个单元 8 位所以数据线需要 8 根:I/O7 ~ I/O0

PPT 里 6116 的容量是 2K×8bit,即 2048 字节,并列出了地址线 A10~A0、数据线 I/O7~I/O0 等主要引脚。


5.2.2 DRAM 动态 RAM

1. DRAM 的特点

DRAM = Dynamic RAM,动态随机存储器特点:- 速度比 SRAM 慢- 用电容存储电荷- 状态不稳定- 必须定时刷新- 单元电路简单- 集成度高- 功耗小- 价格低- 常用于主存/内存条

PPT 中给出 DRAM 的典型刷新间隔为 2ms,并指出它主要用于构造内存条。

2. SRAM 和 DRAM 对比

对比项 SRAM DRAM
存储原理 触发器 电容
是否刷新 不需要 需要
速度 较慢
集成度
功耗 较大 较小
成本
典型用途 Cache 主存

记忆口诀:
S = Stable = 稳定,不刷新,贵且快D = Dynamic = 动态,要刷新,便宜容量大


5.2.3 DRAM 为什么要刷新?

DRAM 单元靠电容有没有电荷表示 0/1。问题是:电容会漏电

所以 DRAM 不是写进去就永久稳定,而是要周期性把数据读出、放大、再写回去,这就叫刷新。

PPT 里 2164A 的刷新操作是:刷新时只接收行地址,选中若干行单元,把信息送到读出放大器放大后再写回原单元;刷新时 DRAM 不能被读写。

刷新方式:异步刷新

典型刷新间隔:2ms2164A 共 128 个刷新周期每隔:2ms / 128 = 15.6μs刷新一行。

PPT 中也给出异步刷新按 15.6μs 间隔依次刷新,并由专门 DRAM 控制器控制。


5.3 ROM 只读存储器

5.3.1 ROM 的核心特点

ROM = Read Only Memory特点:- 正常工作时主要读出- 掉电后内容不丢失- 常用于存放 BIOS、固定程序、表格、常量等

你可以把 RAM 和 ROM 这样区分:
RAM:运行时临时放数据,掉电丢失ROM:长期保存固定内容,掉电不丢


5.3.2 EPROM 27128

PPT 重点讲了 EPROM 27128。它的容量是:
27128 = 16K × 8

所以:
16K = 2^14地址线:14 根,A13 ~ A0数据线:8 根,D7 ~ D0

PPT 中 27128 内部包括存储阵列、X/Y 译码器、输出允许/片选/编程逻辑、数据输出缓冲器;其容量为 16K×8,A13~A0 共 14 根地址线,D7~D0 为 8 位数据线。

EPROM 的常见引脚理解

A13 ~ A0:地址线,用来选择芯片内部哪个单元D7 ~ D0:数据线,用来输出/编程输入数据CE:片选,决定这片芯片是否被选中OE:输出允许,读出时让数据出现在数据线上PGM:编程控制VPP:编程电压VCC/GND:电源和地

5.3.3 EEPROM 2817A

EEPROM = Electrically Erasable Programmable ROM特点:- 可电擦除- 可电写入- 编程前能自动擦除要写入的单元

PPT 中 EEPROM 2817A 容量为 2KB,存取时间 150ns,写入时间和擦除时间约 10ms。


5.4 存储器与 CPU 的连接

这部分是第5章最容易出大题的地方。
5.4.1 存储器接口设计要考虑什么?

存储器与CPU连接时,要考虑:1. 地址总线怎么连2. 数据总线怎么连3. 控制总线怎么连4. CPU负载能力是否够5. 存储器速度是否跟得上CPU时序6. 地址空间如何分配7. 片选信号如何产生

PPT 中明确说,要考虑 CPU 总线负载能力、CPU 时序与存储器存取速度配合、地址分配和片选、控制信号连接。


5.4.2 地址线:高位片选,低位片内寻址

这是本节的灵魂。
低位地址线:接到存储芯片的地址引脚,用来选择芯片内部哪个单元。高位地址线:送入译码器,用来产生片选信号,决定选中哪一片芯片。

PPT 中说,通常 CPU 地址信息的高位用来生成片选信号,低位直接连到芯片地址线上,实现片内寻址。


5.4.3 片选信号 CS/CE

CPU 地址空间很大,但一片芯片容量有限。

所以访问某个地址时,系统要先判断:
这个地址属于哪一片芯片?

选中芯片的信号就叫:
片选信号:CS 或 CE

常见片选信号是低电平有效
CE = 0:芯片被选中CE = 1:芯片不工作


5.4.4 74LS138 译码器

74LS138 = 3-8译码器输入:C B A 三位二进制输出:Y0 ~ Y7 八个输出特点:某一时刻只有一个输出为低电平

PPT 中说 74LS138 输入 3 位二进制码,在 8 个输出中产生 1 个低电平片选信号。

可以这样记:
CBA = 000 -> Y0 = 0CBA = 001 -> Y1 = 0CBA = 010 -> Y2 = 0…CBA = 111 -> Y7 = 0


5.4.5 存储空间扩展

1. 位扩展

位扩展:增加每个存储单元的位数

比如 CPU 数据总线是 8 位,但芯片是:
64K × 1

一片只能提供 1 位数据,所以要 8 片并联:
8片 64K × 1 -> 64K × 8

PPT 中的例子就是用 8 片 64K×1 芯片扩展成 64KB 存储器,各片地址线并接,各 I/O 脚分别接 D7~D0。

2. 字扩展

字扩展:增加存储单元个数,也就是扩大地址范围

例如:
16K × 8 -> 64K × 8

一片是 16K,要变成 64K:
64K / 16K = 4所以需要 4 片。

PPT 中例 5.3 正是用 4 个 16K×8 芯片字扩展为 64K×8,并用 2-4 译码器产生片选信号。

3. 字位扩展

字位扩展 = 位数不够 + 字数不够

例如:
用 1K × 4 构成 4K × 8

步骤:
第一步:位扩展2片 1K×4 -> 1K×8第二步:字扩展4组 1K×8 -> 4K×8

PPT 例 5.4 正是用 1K×4 的 2114 构成 4K×8:先 2 片并成 1KB,再 4 组字扩展。


5.4.6 三种片选方法

1. 线选法

用某几根高位地址线直接作为片选信号。

优点:
电路简单

缺点:
很多高位地址没有参与译码会造成地址重叠地址空间浪费

PPT 中线选法使用某 1 位高位地址做片选,电路简单但空间浪费大,会导致地址重叠和地址不连续。


2. 全译码法

所有高位地址线都参与译码。

优点:
每个存储单元地址唯一不存在地址重叠

缺点:
译码电路复杂

PPT 中全译码法定义为全部高位地址参与译码,使每个存储单元地址唯一,不存在地址重叠,但译码电路复杂。


3. 部分译码法

只让一部分高位地址参与译码。

特点:
比全译码简单但仍可能产生地址重叠

PPT 中部分译码法只对高位地址中的某几位译码,未参与译码位可以是 0 或 1,因此每个存储单元可能对应多个地址。


6.1 I/O接口

6.1.1 为什么需要 I/O 接口?

CPU 和存储器比较“合拍”,但 CPU 和外设差异太大。
CPU:- 速度快- 电平标准统一- 数据格式规整- 时序严格外设:- 速度慢- 种类多- 电平可能不同- 数据格式可能不同- 工作时序不统一

所以中间要加:
I/O接口电路

PPT 中说,I/O 接口电路是外设与计算机之间传送信息的交接部件,每个外设都要通过接口电路才能与主机相连。


6.1.2 I/O接口的功能

1. 数据缓冲   解决CPU快、外设慢的问题。2. 电平转换   解决TTL、RS-232等电平不一致问题。3. 信息格式转换   例如模拟量/数字量转换,串行/并行转换。4. 时序控制   通过状态、应答、中断等信号协调CPU和外设。5. 地址译码   判断CPU当前访问的是哪个I/O端口。6. 提供读/写、中断等控制逻辑

6.1.3 I/O端口

CPU 和外设交换的信息主要有三类:
1. 数据信息2. 状态信息3. 控制信息

所以端口也分三类:

端口类型 作用
数据端口 存放输入/输出数据
状态端口 让 CPU 读取外设状态
控制/命令端口 CPU 向接口发命令

PPT 中把 I/O 端口定义为接口电路中相关寄存器及控制逻辑,CPU 可直接读写端口信息。


6.1.4 常见状态位

Ready:准备好。输入时表示有数据等CPU读;输出时表示输出缓冲器空,可以接收新数据。Busy:忙。表示外设正在工作,暂时不能接收新数据。Error:错误。表示传送过程中发生错误。

6.1.5 I/O端口寻址方式

1. 存储器映像寻址

I/O端口和内存统一编址。

特点:
优点:访存指令都能访问I/O端口缺点:占用内存地址空间

2. I/O单独编址

I/O端口单独拥有地址空间。CPU用专门的 IN / OUT 指令访问。

PPT 中说 8086 系统采用 I/O 单独编址方式。


6.1.6 CPU与外设的数据传送方式

1. 程序控制方式

又分两种:
无条件传送:默认外设随时准备好,直接 IN/OUT。条件传送 / 查询方式:CPU先读状态口,确认Ready/Busy,再传送数据。

PPT 中说明程序控制方式由程序完成,分为无条件传送和条件传送;条件传送要先读取状态口,反复检测外设是否准备好。

2. 中断方式

CPU平时做自己的事。外设准备好后发中断请求。CPU响应中断,执行中断服务程序完成一次数据传送。

PPT 中提到查询方式会让 CPU 大量等待,而中断方式下 CPU 平时执行主程序,外设准备好后再请求 CPU 服务。

3. DMA方式

DMA = Direct Memory Access直接存储器存取

特点:
CPU让出总线控制权DMA控制器接管地址总线、数据总线、控制总线外设和内存直接批量传送数据传完后归还总线

PPT 中 DMA 控制器可以给出内存地址、修改地址指针、设定传送字节数,并向内存和外设发读写控制信号。


6.2 8255A 工作原理

6.2.1 8255A是什么?

8255A = 可编程并行I/O接口芯片PPI = Programmable Peripheral Interface

它可以通过编程工作在三种方式:
方式0:基本输入输出方式1:选通输入输出方式2:双向总线方式

PPT 中明确 8255A 是通用可编程并行 I/O 接口芯片,可通过编程工作于 3 种数据传输方式。


6.2.2 8255A内部结构

8255A内部主要包括:1. 数据端口A、B、C2. A组和B组控制逻辑3. 数据总线缓冲器4. 读/写控制逻辑

PPT 中给出 8255A 有端口 A/B/C,其中 C 口可分成上半部和下半部,还有 A组/B组控制逻辑、数据总线缓冲器、读写控制逻辑。


6.2.3 三个端口

A口:8位,PA7 ~ PA0B口:8位,PB7 ~ PB0C口:8位,PC7 ~ PC0

C口特殊:
C口可以拆成:- C高:PC7 ~ PC4- C低:PC3 ~ PC0C口还常用作A口、B口的联络/状态控制信号。


6.2.4 A组和B组

A组:- 管理A口- 管理C口高4位 PC7 ~ PC4B组:- 管理B口- 管理C口低4位 PC3 ~ PC0

这点很重要,因为控制字里会分别设置 A组、B组。


6.2.5 端口地址选择

8255A 用 A1A0 选择内部端口:

A1A0 选择
00 A口
01 B口
10 C口
11 控制口

PPT 中给出:若 8255A 基地址为 60H,则 A口、B口、C口、控制口分别为 60H、61H、62H、63H。

在 8086 的 16 位系统中,如果 8255A 的 D7~D0 接低 8 位数据总线,则通常用偶地址访问。例如基地址 F0H 时,A/B/C/控制口分别是 F0H、F2H、F4H、F6H。


6.2.6 8255A工作方式

方式0:基本输入输出

适合简单I/O不需要应答信号A口、B口可作8位输入/输出C高、C低可作4位输入/输出输出可锁存输入一般不锁存

PPT 中说方式0适用于不需要应答信号的简单 I/O,A口、B口作8位端口,C高和C低作两个4位端口或合成8位端口。

方式1:选通输入/输出

适合需要握手信号的I/OA口、B口作为数据口C口部分引脚作为联络信号支持中断请求

PPT 中方式1又称选通输入/输出方式,A口、B口作数据口,PC2~PC0、PC5~PC3 用作联络信号。

典型联络信号:
STB:选通信号IBF:输入缓冲器满OBF:输出缓冲器满ACK:外设应答INTR:中断请求INTE:中断允许

方式2:双向总线方式

只有A口支持方式2A口可双向传送数据输入、输出都可锁存C口提供联络信号

PPT 中说方式2也称双向总线方式,只有 A口支持,A口既能输入也能输出。


6.3 8255A应用举例

6.3.1 例1:开关状态检测 + LED显示

题意:
8个开关 K7 ~ K08个LED LED7 ~ LED0要求:开关断开 -> 对应LED亮开关闭合 -> 对应LED灭

硬件思路:
A口输入:接开关B口输出:接LEDC口不用

PPT 中例1就是 A口输入接 K7~K0,B口输出接 LED7~LED0,端口地址为 F0H、F2H、F4H、F6H,分别对应 A口、B口、C口、控制口。

控制字:
A口方式0输入B口方式0输出C口不用控制字 = 10010000B = 90H

程序逻辑:
MOV DX, 0F6HMOV AL, 10010000BOUT DX, ALTEST_IT: MOV DX, 0F0H IN AL, DX MOV DX, 0F2H OUT DX, AL CALL DELAY JMP TEST_IT

核心不是背程序,而是看懂:
初始化8255A -> 从A口读开关 -> 向B口输出 -> LED显示 -> 循环


6.3.2 例2:开关状态用七段LED显示

题意:
A口接4个开关 K3 ~ K0B口接七段LED显示器4位开关共有16种状态,显示0~F

PPT 中 A口方式0输入,B口方式0输出;PB0~PB6 接 a~g 段,通过 74LS04 反相驱动。

七段码表:
TABLE DB 40H,79H,24H,30H,19H,12H,02H,78H DB 00H,18H,80H,03H,43H,21H,06H,0EH

程序核心:
IN AL, 60H ; 读A口AND AL, 0FH ; 只取低4位MOV BX, OFFSET TABLEXLAT ; 查表OUT 61H, AL ; 输出七段码

这里最关键的是:
开关状态不是直接输出到七段LED。而是:开关状态 -> 作为表下标 -> 查七段码 -> 输出到B口


本轮小结

第5章核心:1. RAM分SRAM和DRAM2. SRAM快、不刷新、贵,用于Cache3. DRAM慢、要刷新、便宜,用于主存4. ROM掉电不丢,EPROM/EEPROM可编程5. 存储器连接重点是地址线、数据线、控制线、片选6. 高位地址译码片选,低位地址片内寻址7. 扩展方式:位扩展、字扩展、字位扩展8. 译码方式:线选、全译码、部分译码第6章核心:1. I/O接口解决CPU与外设速度、电平、格式、时序不匹配2. 端口分数据口、状态口、控制口3. 传送方式:程序控制、中断、DMA4. 8255A有A/B/C三个端口5. 8255A有方式0、方式1、方式26. 应用题一般是:确定端口方向 -> 写控制字 -> IN读 -> OUT写

先检查一个小问题,看看第5章地基稳不稳:
某 SRAM 芯片标为 8K × 8,它需要多少根地址线?多少根数据线?

你先答这个,我们再继续把 “存储器连接题怎么做” 细讲一遍。

例1我没太看懂啊

Thought for 22s

可以,这个例1其实就一句话:
8255A 把“开关状态”从 A口读进来,再原封不动从 B口送出去,让 LED 显示。

你先别管图,我们把它拆成 4步


例1:8个开关控制8个LED

  1. 题目要干什么?

有 8 个开关:K7 ~ K0有 8 个 LED:LED7 ~ LED0要求:- 开关断开 -> 对应 LED 亮- 开关闭合 -> 对应 LED 灭

PPT 里这个例子就是“不断检测 K7~K0 的通断状态,并显示在 LED7~LED0 上”。


  1. 为什么用 A口和 B口?

8255A 有 A、B、C 三个端口。

这个题里:
A口:输入口- PA7 ~ PA0 接 8 个开关 K7 ~ K0- 作用:读开关状态B口:输出口- PB7 ~ PB0 接 8 个 LED- 作用:控制 LED 亮灭C口:不用

所以电路逻辑是:
开关 K7K0 ↓8255A A口输入 ↓CPU读入AL ↓CPU再输出AL ↓8255A B口输出 ↓LED7LED0

一句人话:A口负责“看”,B口负责“说”。


  1. 为什么控制字是 10010000B

8255A 上电后不知道 A口、B口该输入还是输出,所以 CPU 要先写一个控制字告诉它:
A口:方式0输入B口:方式0输出C口:不用

控制字:
MOV AL, 10010000BOUT 0F6H, AL

拆开看:
10010000BD7 = 1:这是“方式选择控制字”D6D5 = 00:A组方式0D4 = 1:A口输入D3 = 0:C口高4位输出/不用D2 = 0:B组方式0D1 = 0:B口输出D0 = 0:C口低4位输出/不用

所以它的含义就是:
把8255A设置成:A口输入,B口输出,方式0。

PPT 中也写了:A口方式0输入,B口方式0输出,C口未用,控制字为 10010000


  1. 端口地址为什么是 F0H、F2H、F6H?

PPT 给出本例中 8255A 的 4 个端口地址:

端口 地址
A口 F0H
B口 F2H
C口 F4H
控制口 F6H

也就是说:
0F0H -> A口0F2H -> B口0F6H -> 控制口

所以程序里:
MOV DX, 0F6HOUT DX, AL

意思是:
把控制字写入控制口,初始化8255A。

然后:
MOV DX, 0F0HIN AL, DX

意思是:
从A口读开关状态到AL。

再然后:
MOV DX, 0F2HOUT DX, AL

意思是:
把AL里的开关状态输出到B口,让LED显示。

PPT 程序也正是这个流程:先向 F6H 写控制字,再从 F0H 读 A口,最后向 F2H 写 B口。


  1. 最关键的一点:为什么读进来能直接输出?

因为这个硬件刚好满足:
开关断开 -> A口读到 1开关闭合 -> A口读到 0B口输出 1 -> LED亮B口输出 0 -> LED灭

题目要求是:
开关断开 -> LED亮开关闭合 -> LED灭

完全对应上了!

所以不需要取反,不需要判断,直接:
IN AL, DX ; 读开关OUT DX, AL ; 输出给LED

就行。

举个例子:
假设 8 个开关状态读入后:AL = 10110010B

那么输出到 B口后:
PB7~PB0 = 10110010B

于是:
值为1的那些LED亮值为0的那些LED灭

也就是 LED 状态和开关断开/闭合状态一一对应。


  1. 整个程序用白话翻译

    MOV DX, 0F6HMOV AL, 10010000BOUT DX, AL

翻译:
告诉8255A:A口做输入,B口做输出。


TEST_IT:    MOV DX, 0F0H    IN AL, DX

翻译:
从A口读取8个开关的状态,放到AL里。


    MOV DX, 0F2H    OUT DX, AL

翻译:
把刚才读到的8位状态,送到B口控制8个LED。


    CALL DELAY_20S    JMP TEST_IT

翻译:
延时一下,然后继续检测。

所以完整逻辑就是:
初始化8255A↓读A口开关↓写B口LED↓延时↓回去继续读


你抓住这个模型就行

A口 = 输入 = 看开关B口 = 输出 = 控制LED控制口 = 配置8255A怎么工作

程序的骨架:
写控制字循环: 从A口读 向B口写 延时 跳回循环


我问你一个小检查:
在这个例子里,IN AL, DX 执行时,DX = 0F0H,那 CPU 读的是 A口、B口、C口还是控制口

这个控制字怎么得到的

Thought for 15s

对,这个点特别关键。8255A 的控制字不是“算”出来的,是按位填表填出来的。

PPT 里说:方式选择控制字写入控制口,用来定义各端口工作方式,并且要求 D7=1D6D5 选 A 口方式,D2 选 B 口方式,D4D3D1D0 分别决定 A口、C高、B口、C低是输入还是输出,且 1=输入,0=输出


8255A 方式选择控制字格式

D7 D6 D5 D4 D3 D2 D1 D0

每一位含义如下:

含义 本题怎么填
D7 方式选择控制字标志 必须为 1
D6D5 A组工作方式 方式0,所以 00
D4 A口方向 A口输入,所以 1
D3 C口高4位方向 C口不用,清0,所以 0
D2 B组工作方式 方式0,所以 0
D1 B口方向 B口输出,所以 0
D0 C口低4位方向 C口不用,清0,所以 0

6.3 8255A应用举例

6.3.1 例1:开关状态检测 + LED显示

1
2
3
4
5
6
8个开关 K7 ~ K0
8个LED LED7 ~ LED0

要求:
开关断 对应LED亮
开关闭合 对应LED灭

硬件思路:

1
2
3
A口输入:开关
B口输入:接LED
C口不用

控制字:

1
2
3
4
A口方式0输入
B口方式0输出
C口不用
控制字 = 10010000B = 90H

程序逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
mov dx, 0f6h
mov al, 10010000b
out dx, al ;写入控制字

TEST_IT:
mov dx, 0f0h
in al, dx

mov dx, 0f2h
out dx, al

call delay
jmp TEST_IT